From f4d9f4f43d89c768bba16533a53e9370a685c351 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Sep 2022 09:23:06 +0200 Subject: [PATCH 01/40] savepoint --- Sources/WalletConnectPairing/PairingClient.swift | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 Sources/WalletConnectPairing/PairingClient.swift diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift new file mode 100644 index 000000000..98c45a723 --- /dev/null +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -0,0 +1,6 @@ +import Foundation + +class PairingClient { + + +} From f59f7bfb24305afd6de14e18d128de0e1b9a5222 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Sep 2022 09:55:55 +0200 Subject: [PATCH 02/40] savepoint --- .../WalletConnectPairing/PairingClient.swift | 28 +++++++++- .../Services/App/AppPairService.swift | 26 +++++++++ .../Services/Wallet/WalletPairService.swift | 38 +++++++++++++ .../Wallet/WalletRequestSubscriber.swift | 55 +++++++++++++++++++ 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 Sources/WalletConnectPairing/Services/App/AppPairService.swift create mode 100644 Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift create mode 100644 Sources/WalletConnectPairing/Services/Wallet/WalletRequestSubscriber.swift diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 98c45a723..7c2b06efd 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -1,6 +1,32 @@ import Foundation +import WalletConnectUtils class PairingClient { + private let walletPairService: WalletPairService + private let appPairService: AppPairService + + + init(appPairService: AppPairService, + walletPairService: WalletPairService + ) { + self.appPairService = appPairService + self.walletPairService = walletPairService + } + /// For wallet to establish a pairing and receive an authentication request + /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future authentication request. + /// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp or delivered with universal linking. + /// + /// Throws Error: + /// - When URI is invalid format or missing params + /// - When topic is already in use + public func pair(uri: WalletConnectURI) async throws { + try await walletPairService.pair(uri) + } + + public func create() async throws -> WalletConnectURI { + return try await appPairService.create() + } + + public func addSubscriber() - } diff --git a/Sources/WalletConnectPairing/Services/App/AppPairService.swift b/Sources/WalletConnectPairing/Services/App/AppPairService.swift new file mode 100644 index 000000000..9d223d06a --- /dev/null +++ b/Sources/WalletConnectPairing/Services/App/AppPairService.swift @@ -0,0 +1,26 @@ +import Foundation +import WalletConnectKMS +import WalletConnectNetworking +import WalletConnectUtils + +actor AppPairService { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private let pairingStorage: WCPairingStorage + + init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, pairingStorage: WCPairingStorage) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.pairingStorage = pairingStorage + } + + func create() async throws -> WalletConnectURI { + let topic = String.generateTopic() + try await networkingInteractor.subscribe(topic: topic) + let symKey = try! kms.createSymmetricKey(topic) + let pairing = WCPairing(topic: topic) + let uri = WalletConnectURI(topic: topic, symKey: symKey.hexRepresentation, relay: pairing.relay, api: .auth) + pairingStorage.setPairing(pairing) + return uri + } +} diff --git a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift new file mode 100644 index 000000000..6dd6b5e40 --- /dev/null +++ b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift @@ -0,0 +1,38 @@ +import Foundation +import WalletConnectKMS +import WalletConnectNetworking +import WalletConnectUtils + +actor WalletPairService { + enum Errors: Error { + case pairingAlreadyExist + } + + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private let pairingStorage: WCPairingStorage + + init(networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + pairingStorage: WCPairingStorage) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.pairingStorage = pairingStorage + } + + func pair(_ uri: WalletConnectURI) async throws { + guard !hasPairing(for: uri.topic) else { + throw Errors.pairingAlreadyExist + } + var pairing = WCPairing(uri: uri) + try await networkingInteractor.subscribe(topic: pairing.topic) + let symKey = try SymmetricKey(hex: uri.symKey) + try kms.setSymmetricKey(symKey, for: pairing.topic) + pairing.activate() + pairingStorage.setPairing(pairing) + } + + func hasPairing(for topic: String) -> Bool { + return pairingStorage.hasPairing(forTopic: topic) + } +} diff --git a/Sources/WalletConnectPairing/Services/Wallet/WalletRequestSubscriber.swift b/Sources/WalletConnectPairing/Services/Wallet/WalletRequestSubscriber.swift new file mode 100644 index 000000000..b22de980d --- /dev/null +++ b/Sources/WalletConnectPairing/Services/Wallet/WalletRequestSubscriber.swift @@ -0,0 +1,55 @@ +import Foundation +import Combine +import JSONRPC +import WalletConnectUtils +import WalletConnectKMS +import WalletConnectNetworking + +protocol PairingRequestSubscriber { + func subscribeForRequest() +} + +class PushRequestSubscriber: PairingRequestSubscriber { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private var publishers = [AnyCancellable]() + var onRequest: ((AuthRequest) -> Void)? + + init(networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + kms: KeyManagementServiceProtocol) { + self.networkingInteractor = networkingInteractor + self.kms = kms + subscribeForRequest() + } + + func subscribeForRequest() { + + networkingInteractor.requestSubscription(on: AuthProtocolMethod.authRequest) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + + }.store(in: &publishers) + } +} + +enum PushProtocolMethod: String, ProtocolMethod { +case authRequest = "wc_pushRequest" + +var method: String { + return self.rawValue +} + +var requestTag: Int { + switch self { + case .authRequest: + return 3000 + } +} + +var responseTag: Int { + switch self { + case .authRequest: + return 3001 + } +} +} From f9f87f2a5a79314d627929151752576cad140e16 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 12 Sep 2022 14:56:18 +0200 Subject: [PATCH 03/40] savepoint --- Example/ExampleApp.xcodeproj/project.pbxproj | 4 + .../IntegrationTests/Auth/PairingTest.swift | 83 +++++++++++++++++++ .../WalletConnectPairing/PairingClient.swift | 25 ++++-- .../Push/PairingClientFactory.swift | 45 ++++++++++ .../Push/PushClient.swift | 42 ++++++++++ .../Push/PushProtocolMethod.swift | 26 ++++++ .../Push/PushRequestSubscriber.swift | 61 ++++++++++++++ .../Services/Wallet/WalletPairService.swift | 4 +- .../Wallet/WalletRequestSubscriber.swift | 55 ------------ 9 files changed, 283 insertions(+), 62 deletions(-) create mode 100644 Example/IntegrationTests/Auth/PairingTest.swift create mode 100644 Sources/WalletConnectPairing/Push/PairingClientFactory.swift create mode 100644 Sources/WalletConnectPairing/Push/PushClient.swift create mode 100644 Sources/WalletConnectPairing/Push/PushProtocolMethod.swift create mode 100644 Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift delete mode 100644 Sources/WalletConnectPairing/Services/Wallet/WalletRequestSubscriber.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index bafe7c5f1..6ee905a66 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */ = {isa = PBXBuildFile; productRef = 8448F1D327E4726F0000B866 /* WalletConnect */; }; 84494388278D9C1B00CC26BB /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84494387278D9C1B00CC26BB /* UIAlertController.swift */; }; 8460DCFC274F98A10081F94C /* RequestViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8460DCFB274F98A10081F94C /* RequestViewController.swift */; }; + 84AA01DD28CF2845005D48D8 /* PairingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DC28CF2845005D48D8 /* PairingTest.swift */; }; 84CE641F27981DED00142511 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE641E27981DED00142511 /* AppDelegate.swift */; }; 84CE642127981DED00142511 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE642027981DED00142511 /* SceneDelegate.swift */; }; 84CE642827981DF000142511 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 84CE642727981DF000142511 /* Assets.xcassets */; }; @@ -219,6 +220,7 @@ 76B6E39E2807A3B6004DF775 /* WalletViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WalletViewController.swift; sourceTree = ""; }; 84494387278D9C1B00CC26BB /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = ""; }; 8460DCFB274F98A10081F94C /* RequestViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestViewController.swift; sourceTree = ""; }; + 84AA01DC28CF2845005D48D8 /* PairingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingTest.swift; sourceTree = ""; }; 84CE641C27981DED00142511 /* DApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 84CE641E27981DED00142511 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 84CE642027981DED00142511 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -581,6 +583,7 @@ 84D2A66728A4F5260088AE09 /* Auth */ = { isa = PBXGroup; children = ( + 84AA01DC28CF2845005D48D8 /* PairingTest.swift */, 84D2A66528A4F51E0088AE09 /* AuthTests.swift */, ); path = Auth; @@ -1458,6 +1461,7 @@ 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */, 7694A5262874296A0001257E /* RegistryTests.swift in Sources */, A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */, + 84AA01DD28CF2845005D48D8 /* PairingTest.swift in Sources */, A5E03E0D28646AD200888481 /* RelayClientEndToEndTests.swift in Sources */, A501AC2728C8E59800CEAA42 /* URLConfig.swift in Sources */, A5E03DFA286465C700888481 /* SignClientTests.swift in Sources */, diff --git a/Example/IntegrationTests/Auth/PairingTest.swift b/Example/IntegrationTests/Auth/PairingTest.swift new file mode 100644 index 000000000..c5d6fee88 --- /dev/null +++ b/Example/IntegrationTests/Auth/PairingTest.swift @@ -0,0 +1,83 @@ +import Foundation +import XCTest +import WalletConnectUtils +@testable import WalletConnectKMS +import WalletConnectRelay +import Combine +import WalletConnectNetworking +import WalletConnectPairing + + +final class Pairingtests: XCTestCase { + var appPairingClient: PairingClient! + var walletPairingClient: PairingClient! + + private var publishers = [AnyCancellable]() + + override func setUp() { + appPairingClient = makeClient(prefix: "👻 App") + walletPairingClient = makeClient(prefix: "🤑 Wallet") + + let expectation = expectation(description: "Wait Clients Connected") + expectation.expectedFulfillmentCount = 2 + + appPairingClient.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + walletPairingClient.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + wait(for: [expectation], timeout: 5) + } + + + func makeClient(prefix: String) -> PairingClient { + let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) + let projectId = "3ca2919724fbfa5456a25194e369a8b4" + let keychain = KeychainStorageMock() + let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) + + let pairingClient = PairingClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) + return pairingClient + } + + func makePushClient() -> PushClient { + let logger = ConsoleLogger(suffix: "", loggingLevel: .debug) + let projectId = "3ca2919724fbfa5456a25194e369a8b4" + let keychain = KeychainStorageMock() + let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) + return PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) + } + + func testProposePushOnPairing() async { + let exp = expectation(description: "") + let appPushClient = makePushClient() + let walletPushClient = makePushClient() + + appPairingClient.configure(with: [appPushClient]) + + walletPairingClient.configure(with: [walletPushClient, KYC]) + + let uri = try! await appPairingClient.create() + + try! await walletPairingClient.pair(uri: uri) + + try! await appPushClient.propose(topic: uri.topic) + + walletPushClient.proposalPublisher.sink { _ in + exp.fulfill() + print("received push proposal") + }.store(in: &publishers) + + wait(for: [exp], timeout: 2) + + } + +} + diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 7c2b06efd..10a760784 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -1,16 +1,22 @@ import Foundation import WalletConnectUtils +import WalletConnectRelay +import Combine -class PairingClient { +public class PairingClient { private let walletPairService: WalletPairService private let appPairService: AppPairService - - + public let socketConnectionStatusPublisher: AnyPublisher + let logger: ConsoleLogging init(appPairService: AppPairService, - walletPairService: WalletPairService + logger: ConsoleLogging, + walletPairService: WalletPairService, + socketConnectionStatusPublisher: AnyPublisher ) { self.appPairService = appPairService self.walletPairService = walletPairService + self.socketConnectionStatusPublisher = socketConnectionStatusPublisher + self.logger = logger } /// For wallet to establish a pairing and receive an authentication request /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future authentication request. @@ -27,6 +33,15 @@ class PairingClient { return try await appPairService.create() } - public func addSubscriber() + public func configure(with paringables: [Paringable]) { + var p = paringables.first! + + p.pairingRequestSubscriber = PairingRequestSubscriber(networkingInteractor: walletPairService.networkingInteractor, logger: logger, kms: walletPairService.kms, protocolMethod: p.protocolMethod) + + + p.pairingRequester = PairingRequester(networkingInteractor: walletPairService.networkingInteractor, kms: walletPairService.kms, logger: logger, protocolMethod: p.protocolMethod) + } + + } diff --git a/Sources/WalletConnectPairing/Push/PairingClientFactory.swift b/Sources/WalletConnectPairing/Push/PairingClientFactory.swift new file mode 100644 index 000000000..8410ee0a3 --- /dev/null +++ b/Sources/WalletConnectPairing/Push/PairingClientFactory.swift @@ -0,0 +1,45 @@ +import Foundation +import WalletConnectRelay +import WalletConnectUtils +import WalletConnectKMS +import WalletConnectNetworking + +public struct PairingClientFactory { + public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PairingClient { + let kms = KeyManagementService(keychain: keychainStorage) + let serializer = Serializer(kms: kms) + let kv = RuntimeKeyValueStorage() + let historyStorage = CodableStore(defaults: kv, identifier: "") + let history = RPCHistory(keyValueStore: historyStorage) + + + let networkingInt = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) + let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: kv, identifier: ""))) + + + let appPairService = AppPairService(networkingInteractor: networkingInt, kms: kms, pairingStorage: pairingStore) + + let walletPaS = WalletPairService(networkingInteractor: networkingInt, kms: kms, pairingStorage: pairingStore) + + return PairingClient(appPairService: appPairService, logger: logger, walletPairService: walletPaS, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher) + } +} + + + +public struct PushClientFactory { + public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PushClient { + let kms = KeyManagementService(keychain: keychainStorage) + let serializer = Serializer(kms: kms) + let kv = RuntimeKeyValueStorage() + let historyStorage = CodableStore(defaults: kv, identifier: "") + let history = RPCHistory(keyValueStore: historyStorage) + + + let networkingInt = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) + + + + return PushClient(networkingInteractor: networkingInt, logger: logger, kms: kms) + } +} diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift new file mode 100644 index 000000000..15164aa79 --- /dev/null +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -0,0 +1,42 @@ +import Foundation +import WalletConnectKMS +import WalletConnectUtils +import WalletConnectNetworking +import Combine + +public class PushClient: Paringable { + + public var protocolMethod: ProtocolMethod + public var proposalPublisher: AnyPublisher { + proposalPublisherSubject.eraseToAnyPublisher() + } + private let proposalPublisherSubject = PassthroughSubject() + + public var pairingRequestSubscriber: PairingRequestSubscriber! { + didSet { + handleProposal() + } + } + + public var pairingRequester: PairingRequester! + + public let logger: ConsoleLogging + + init(networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + kms: KeyManagementServiceProtocol) { + self.logger = logger + + protocolMethod = PushProtocolMethod.propose + } + + func handleProposal() { + pairingRequestSubscriber.onRequest = { [unowned self] _ in + logger.debug("Push: received proposal") + } + } + + public func propose(topic: String) async throws { + try await pairingRequester.request(topic: topic) + } +} diff --git a/Sources/WalletConnectPairing/Push/PushProtocolMethod.swift b/Sources/WalletConnectPairing/Push/PushProtocolMethod.swift new file mode 100644 index 000000000..0b6772da0 --- /dev/null +++ b/Sources/WalletConnectPairing/Push/PushProtocolMethod.swift @@ -0,0 +1,26 @@ +import Foundation +import WalletConnectNetworking + +enum PushProtocolMethod: String, ProtocolMethod { + case propose = "wc_pushPropose" + + var method: String { + return self.rawValue + } + + var requestTag: Int { + switch self { + case .propose: + return 3000 + } + } + + var responseTag: Int { + switch self { + case .propose: + return 3001 + } + } +} + +struct PushRequestParams: Codable {} diff --git a/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift b/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift new file mode 100644 index 000000000..d0f04445f --- /dev/null +++ b/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift @@ -0,0 +1,61 @@ +import Foundation +import Combine +import JSONRPC +import WalletConnectUtils +import WalletConnectKMS +import WalletConnectNetworking + +public protocol Paringable { + var protocolMethod: ProtocolMethod { get set } + var pairingRequestSubscriber: PairingRequestSubscriber! {get set} + var pairingRequester: PairingRequester! {get set} +} + +public class PairingRequestSubscriber { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private var publishers = [AnyCancellable]() + var onRequest: ((RequestSubscriptionPayload) -> Void)? + let protocolMethod: ProtocolMethod + + init(networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + kms: KeyManagementServiceProtocol, + protocolMethod: ProtocolMethod) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.protocolMethod = protocolMethod + subscribeForRequest() + } + + func subscribeForRequest() { + + networkingInteractor.requestSubscription(on: protocolMethod) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + onRequest?(payload) + }.store(in: &publishers) + } +} + +public class PairingRequester { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private let logger: ConsoleLogging + let protocolMethod: ProtocolMethod + + init(networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + logger: ConsoleLogging, + protocolMethod: ProtocolMethod) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.logger = logger + self.protocolMethod = protocolMethod + } + + func request(topic: String) async throws { + let request = RPCRequest(method: protocolMethod.method, params: AnyCodable("")) + + try await networkingInteractor.requestNetworkAck(request, topic: topic, tag: PushProtocolMethod.propose.requestTag) + } +} diff --git a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift index 6dd6b5e40..199b5a9a9 100644 --- a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift +++ b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift @@ -8,8 +8,8 @@ actor WalletPairService { case pairingAlreadyExist } - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol + let networkingInteractor: NetworkInteracting + let kms: KeyManagementServiceProtocol private let pairingStorage: WCPairingStorage init(networkingInteractor: NetworkInteracting, diff --git a/Sources/WalletConnectPairing/Services/Wallet/WalletRequestSubscriber.swift b/Sources/WalletConnectPairing/Services/Wallet/WalletRequestSubscriber.swift deleted file mode 100644 index b22de980d..000000000 --- a/Sources/WalletConnectPairing/Services/Wallet/WalletRequestSubscriber.swift +++ /dev/null @@ -1,55 +0,0 @@ -import Foundation -import Combine -import JSONRPC -import WalletConnectUtils -import WalletConnectKMS -import WalletConnectNetworking - -protocol PairingRequestSubscriber { - func subscribeForRequest() -} - -class PushRequestSubscriber: PairingRequestSubscriber { - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private var publishers = [AnyCancellable]() - var onRequest: ((AuthRequest) -> Void)? - - init(networkingInteractor: NetworkInteracting, - logger: ConsoleLogging, - kms: KeyManagementServiceProtocol) { - self.networkingInteractor = networkingInteractor - self.kms = kms - subscribeForRequest() - } - - func subscribeForRequest() { - - networkingInteractor.requestSubscription(on: AuthProtocolMethod.authRequest) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - - }.store(in: &publishers) - } -} - -enum PushProtocolMethod: String, ProtocolMethod { -case authRequest = "wc_pushRequest" - -var method: String { - return self.rawValue -} - -var requestTag: Int { - switch self { - case .authRequest: - return 3000 - } -} - -var responseTag: Int { - switch self { - case .authRequest: - return 3001 - } -} -} From 395962526662dda2e5f1e0a8f6eb55c9513ae14e Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 6 Sep 2022 23:16:50 +0300 Subject: [PATCH 04/40] SessionEngine --- Package.swift | 2 +- .../NetworkInteracting.swift | 4 +- .../NetworkInteractor.swift | 14 +- .../RequestSubscriptionPayload.swift | 2 +- .../ResponseSubscriptionPayload.swift | 2 +- .../SubscriptionPayload.swift | 7 + .../Engine/Common/ApproveEngine.swift | 5 +- .../Engine/Common/SessionEngine.swift | 171 +++--- .../NetworkInteractor/NetworkInteractor.swift | 518 +++++++++--------- .../NetworkInteractor/NetworkRelaying.swift | 38 +- Sources/WalletConnectSign/Reason.swift | 11 - Sources/WalletConnectSign/Request.swift | 9 +- .../WCRequestSubscriptionPayload.swift | 11 - .../WalletConnectSign/Types/ReasonCode.swift | 4 +- .../WalletConnectSign/Types/WCMethod.swift | 37 -- .../WalletConnectSign/Types/WCRequest.swift | 236 +++----- .../WalletConnectSign/Types/WCResponse.swift | 14 - 17 files changed, 473 insertions(+), 612 deletions(-) create mode 100644 Sources/WalletConnectNetworking/SubscriptionPayload.swift delete mode 100644 Sources/WalletConnectSign/Reason.swift delete mode 100644 Sources/WalletConnectSign/Subscription/WCRequestSubscriptionPayload.swift delete mode 100644 Sources/WalletConnectSign/Types/WCMethod.swift delete mode 100644 Sources/WalletConnectSign/Types/WCResponse.swift diff --git a/Package.swift b/Package.swift index 7640db6ad..a991ee8bf 100644 --- a/Package.swift +++ b/Package.swift @@ -32,7 +32,7 @@ let package = Package( targets: [ .target( name: "WalletConnectSign", - dependencies: ["WalletConnectRelay", "WalletConnectUtils", "WalletConnectKMS", "WalletConnectPairing"], + dependencies: ["WalletConnectNetworking", "WalletConnectPairing"], path: "Sources/WalletConnectSign"), .target( name: "Chat", diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 8dd02d382..67d5e9673 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -15,11 +15,11 @@ public protocol NetworkInteracting { func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws func requestSubscription( - on request: ProtocolMethod + on request: ProtocolMethod? ) -> AnyPublisher, Never> func responseSubscription( - on request: ProtocolMethod + on request: ProtocolMethod? ) -> AnyPublisher, Never> func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher diff --git a/Sources/WalletConnectNetworking/NetworkInteractor.swift b/Sources/WalletConnectNetworking/NetworkInteractor.swift index 13859ead6..f5ed34273 100644 --- a/Sources/WalletConnectNetworking/NetworkInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkInteractor.swift @@ -56,9 +56,12 @@ public class NetworkingInteractor: NetworkInteracting { } } - public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { + public func requestSubscription(on request: ProtocolMethod?) -> AnyPublisher, Never> { return requestPublisher - .filter { $0.request.method == request.method } + .filter { rpcRequest in + guard let request = request else { return true } + return rpcRequest.request.method == request.method + } .compactMap { topic, rpcRequest in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil } return RequestSubscriptionPayload(id: id, topic: topic, request: request) @@ -66,9 +69,12 @@ public class NetworkingInteractor: NetworkInteracting { .eraseToAnyPublisher() } - public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { + public func responseSubscription(on request: ProtocolMethod?) -> AnyPublisher, Never> { return responsePublisher - .filter { $0.request.method == request.method } + .filter { rpcRequest in + guard let request = request else { return true } + return rpcRequest.request.method == request.method + } .compactMap { topic, rpcRequest, rpcResponse in guard let id = rpcRequest.id, diff --git a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift index fa6b0e8db..d8767d692 100644 --- a/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift +++ b/Sources/WalletConnectNetworking/RequestSubscriptionPayload.swift @@ -1,7 +1,7 @@ import Foundation import JSONRPC -public struct RequestSubscriptionPayload: Codable { +public struct RequestSubscriptionPayload: Codable, SubscriptionPayload { public let id: RPCID public let topic: String public let request: Request diff --git a/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift b/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift index 21043eb9d..93b21538a 100644 --- a/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift +++ b/Sources/WalletConnectNetworking/ResponseSubscriptionPayload.swift @@ -1,7 +1,7 @@ import Foundation import JSONRPC -public struct ResponseSubscriptionPayload { +public struct ResponseSubscriptionPayload: SubscriptionPayload { public let id: RPCID public let topic: String public let request: Request diff --git a/Sources/WalletConnectNetworking/SubscriptionPayload.swift b/Sources/WalletConnectNetworking/SubscriptionPayload.swift new file mode 100644 index 000000000..ad5e60eab --- /dev/null +++ b/Sources/WalletConnectNetworking/SubscriptionPayload.swift @@ -0,0 +1,7 @@ +import Foundation +import JSONRPC + +public protocol SubscriptionPayload { + var id: RPCID { get } + var topic: String { get } +} diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 229e2e698..a5a310a1d 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -3,6 +3,7 @@ import Combine import WalletConnectUtils import WalletConnectKMS import WalletConnectPairing +import WalletConnectNetworking final class ApproveEngine { enum Errors: Error { @@ -180,10 +181,10 @@ private extension ApproveEngine { }.store(in: &publishers) } - func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) { + func respondError(payload: SubscriptionPayload, reason: ReasonCode) { Task { do { - try await networkingInteractor.respondError(payload: payload, reason: reason) + try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: <#T##Int#>, reason: reason) } catch { logger.error("Respond Error failed with: \(error.localizedDescription)") } diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index a1197c8e1..df321e01a 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -1,11 +1,13 @@ import Foundation import Combine +import JSONRPC import WalletConnectUtils import WalletConnectKMS +import WalletConnectNetworking final class SessionEngine { enum Errors: Error { - case respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) + case respondError(payload: SubscriptionPayload, reason: ReasonCode) case sessionNotFound(topic: String) } @@ -49,15 +51,16 @@ final class SessionEngine { logger.debug("Could not find session to ping for topic \(topic)") return } - networkingInteractor.requestPeerResponse(.wcSessionPing, onTopic: topic) { [unowned self] result in - switch result { - case .success: - logger.debug("Did receive ping response") - completion(.success(())) - case .failure(let error): - logger.debug("error: \(error)") - } - } +// TODO: Ping disabled +// networkingInteractor.requestPeerResponse(.wcSessionPing, onTopic: topic) { [unowned self] result in +// switch result { +// case .success: +// logger.debug("Did receive ping response") +// completion(.success(())) +// case .failure(let error): +// logger.debug("error: \(error)") +// } +// } } func request(_ request: Request) async throws { @@ -71,14 +74,18 @@ final class SessionEngine { } let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params) let sessionRequestParams = SessionType.RequestParams(request: chainRequest, chainId: request.chainId) - try await networkingInteractor.request(.wcSessionRequest(sessionRequestParams), onTopic: request.topic) + + let payload = WCRequest.sessionRequest(sessionRequestParams) + let rpcRequest = RPCRequest(method: WCRequest.Method.sessionRequest.method, params: payload) + try await networkingInteractor.request(rpcRequest, topic: request.topic, tag: WCRequest.Method.sessionRequest.requestTag) } func respondSessionRequest(topic: String, response: JsonRpcResult) async throws { guard sessionStore.hasSession(forTopic: topic) else { throw Errors.sessionNotFound(topic: topic) } - try await networkingInteractor.respond(topic: topic, response: response, tag: 1109) // FIXME: Hardcoded tag + // TODO: ??? +// try await networkingInteractor.respond(topic: topic, response: response, tag: 1109) // FIXME: Hardcoded tag } func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws { @@ -90,7 +97,8 @@ final class SessionEngine { throw WalletConnectError.invalidEvent } let params = SessionType.EventParams(event: event, chainId: chainId) - try await networkingInteractor.request(.wcSessionEvent(params), onTopic: topic) + let rpcRequest = RPCRequest(method: WCRequest.Method.sessionEvent.method, params: params) + try await networkingInteractor.request(rpcRequest, topic: topic, tag: WCRequest.Method.sessionEvent.requestTag) } } @@ -99,100 +107,113 @@ final class SessionEngine { private extension SessionEngine { func setupNetworkingSubscriptions() { - networkingInteractor.wcRequestPublisher.sink { [unowned self] subscriptionPayload in - do { - switch subscriptionPayload.wcRequest.params { - case .sessionDelete(let deleteParams): - try onSessionDelete(subscriptionPayload, deleteParams: deleteParams) - case .sessionRequest(let sessionRequestParams): - try onSessionRequest(subscriptionPayload, payloadParams: sessionRequestParams) + networkingInteractor.socketConnectionStatusPublisher + .sink { [unowned self] status in + guard status == .connected else { return } + sessionStore.getAll() + .forEach { session in + Task { try await networkingInteractor.subscribe(topic: session.topic) } + } + } + .store(in: &publishers) + + networkingInteractor.requestSubscription(on: nil) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + switch payload.request { + case .sessionDelete(let params): + onSessionDelete(payload, params: params) + case .sessionRequest(let params): + onSessionRequest(payload, params: params) case .sessionPing: - onSessionPing(subscriptionPayload) - case .sessionEvent(let eventParams): - try onSessionEvent(subscriptionPayload, eventParams: eventParams) + onSessionPing(payload) + case .sessionEvent(let params): + onSessionEvent(payload, params: params) default: return } - } catch Errors.respondError(let payload, let reason) { - respondError(payload: payload, reason: reason) - } catch { - logger.error("Unexpected Error: \(error.localizedDescription)") } - }.store(in: &publishers) - - networkingInteractor.transportConnectionPublisher - .sink { [unowned self] (_) in - let topics = sessionStore.getAll().map {$0.topic} - topics.forEach { topic in Task { try? await networkingInteractor.subscribe(topic: topic) } } - }.store(in: &publishers) - - networkingInteractor.responsePublisher - .sink { [unowned self] response in - self.handleResponse(response) - }.store(in: &publishers) + .store(in: &publishers) + + networkingInteractor.responseSubscription(on: nil) + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + switch payload.request { + case .sessionRequest(let params): + // TODO: ??? Chain ID from request is ok? + // Need to check absolute string + let response = Response(topic: payload.topic, chainId: params.chainId.absoluteString, result: payload.response) + onSessionResponse?(response) + default: + break + } + } + .store(in: &publishers) } - func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) { + func respondError(payload: SubscriptionPayload, reason: ReasonCode, tag: Int) { Task { do { - try await networkingInteractor.respondError(payload: payload, reason: reason) + try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: tag, reason: reason) } catch { logger.error("Respond Error failed with: \(error.localizedDescription)") } } } - func onSessionDelete(_ payload: WCRequestSubscriptionPayload, deleteParams: SessionType.DeleteParams) throws { + func onSessionDelete(_ payload: SubscriptionPayload, params: SessionType.DeleteParams) { + let tag = WCRequest.Method.sessionDelete.responseTag let topic = payload.topic guard sessionStore.hasSession(forTopic: topic) else { - throw Errors.respondError(payload: payload, reason: .noSessionForTopic) + return respondError(payload: payload, reason: .noSessionForTopic, tag: tag) } sessionStore.delete(topic: topic) networkingInteractor.unsubscribe(topic: topic) - networkingInteractor.respondSuccess(for: payload) - onSessionDelete?(topic, deleteParams) + Task(priority: .high) { + try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: tag) + } + onSessionDelete?(topic, params) } - func onSessionRequest(_ payload: WCRequestSubscriptionPayload, payloadParams: SessionType.RequestParams) throws { + func onSessionRequest(_ payload: SubscriptionPayload, params: SessionType.RequestParams) { + let tag = WCRequest.Method.sessionRequest.responseTag let topic = payload.topic - let jsonRpcRequest = JSONRPCRequest(id: payload.wcRequest.id, method: payloadParams.request.method, params: payloadParams.request.params) let request = Request( - id: jsonRpcRequest.id, - topic: topic, - method: jsonRpcRequest.method, - params: jsonRpcRequest.params, - chainId: payloadParams.chainId) + id: payload.id, + topic: payload.topic, + method: WCRequest.Method.sessionRequest.method, + params: params, + chainId: params.chainId) guard let session = sessionStore.getSession(forTopic: topic) else { - throw Errors.respondError(payload: payload, reason: .noSessionForTopic) + return respondError(payload: payload, reason: .noSessionForTopic, tag: tag) } - let chain = request.chainId - guard session.hasNamespace(for: chain) else { - throw Errors.respondError(payload: payload, reason: .unauthorizedChain) + guard session.hasNamespace(for: request.chainId) else { + return respondError(payload: payload, reason: .unauthorizedChain, tag: tag) } - guard session.hasPermission(forMethod: request.method, onChain: chain) else { - throw Errors.respondError(payload: payload, reason: .unauthorizedMethod(request.method)) + guard session.hasPermission(forMethod: request.method, onChain: request.chainId) else { + return respondError(payload: payload, reason: .unauthorizedMethod(request.method), tag: tag) } onSessionRequest?(request) } - func onSessionPing(_ payload: WCRequestSubscriptionPayload) { - networkingInteractor.respondSuccess(for: payload) + func onSessionPing(_ payload: SubscriptionPayload) { + Task(priority: .high) { + try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: WCRequest.Method.sessionPing.responseTag) + } } - func onSessionEvent(_ payload: WCRequestSubscriptionPayload, eventParams: SessionType.EventParams) throws { - let event = eventParams.event + func onSessionEvent(_ payload: SubscriptionPayload, params: SessionType.EventParams) { + let tag = WCRequest.Method.sessionEvent.responseTag + let event = params.event let topic = payload.topic guard let session = sessionStore.getSession(forTopic: topic) else { - throw Errors.respondError(payload: payload, reason: .noSessionForTopic) + return respondError(payload: payload, reason: .noSessionForTopic, tag: tag) + } + guard session.peerIsController, session.hasPermission(forEvent: event.name, onChain: params.chainId) else { + return respondError(payload: payload, reason: .unauthorizedEvent(event.name), tag: tag) } - guard - session.peerIsController, - session.hasPermission(forEvent: event.name, onChain: eventParams.chainId) - else { - throw Errors.respondError(payload: payload, reason: .unauthorizedEvent(event.name)) + Task(priority: .high) { + try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: tag) } - networkingInteractor.respondSuccess(for: payload) - onEventReceived?(topic, event.publicRepresentation(), eventParams.chainId) + onEventReceived?(topic, event.publicRepresentation(), params.chainId) } func setupExpirationSubscriptions() { @@ -201,14 +222,4 @@ private extension SessionEngine { self?.kms.deleteAgreementSecret(for: session.topic) } } - - func handleResponse(_ response: WCResponse) { - switch response.requestParams { - case .sessionRequest: - let response = Response(topic: response.topic, chainId: response.chainId, result: response.result) - onSessionResponse?(response) - default: - break - } - } } diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift index 3389771b3..f23cc922f 100644 --- a/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift +++ b/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift @@ -1,259 +1,259 @@ -import Foundation -import Combine -import WalletConnectUtils -import WalletConnectKMS - -protocol NetworkInteracting: AnyObject { - var transportConnectionPublisher: AnyPublisher {get} - var wcRequestPublisher: AnyPublisher {get} - var responsePublisher: AnyPublisher {get} - /// Completes when request sent from a networking client - func request(_ wcMethod: WCMethod, onTopic topic: String) async throws - /// Completes with an acknowledgement from the relay network - func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void)) - /// Completes with a peer response - func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) - func respond(topic: String, response: JsonRpcResult, tag: Int) async throws - func respondSuccess(payload: WCRequestSubscriptionPayload) async throws - func respondSuccess(for payload: WCRequestSubscriptionPayload) - func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws - func subscribe(topic: String) async throws - func unsubscribe(topic: String) -} - -extension NetworkInteracting { - func request(_ wcMethod: WCMethod, onTopic topic: String) { - requestPeerResponse(wcMethod, onTopic: topic, completion: nil) - } -} - -class NetworkInteractor: NetworkInteracting { - - private var publishers = Set() - - private var relayClient: NetworkRelaying - private let serializer: Serializing - private let jsonRpcHistory: JsonRpcHistoryRecording - - private let transportConnectionPublisherSubject = PassthroughSubject() - private let responsePublisherSubject = PassthroughSubject() - private let wcRequestPublisherSubject = PassthroughSubject() - - var transportConnectionPublisher: AnyPublisher { - transportConnectionPublisherSubject.eraseToAnyPublisher() - } - var wcRequestPublisher: AnyPublisher { - wcRequestPublisherSubject.eraseToAnyPublisher() - } - var responsePublisher: AnyPublisher { - responsePublisherSubject.eraseToAnyPublisher() - } - - let logger: ConsoleLogging - - init(relayClient: NetworkRelaying, - serializer: Serializing, - logger: ConsoleLogging, - jsonRpcHistory: JsonRpcHistoryRecording) { - self.relayClient = relayClient - self.serializer = serializer - self.logger = logger - self.jsonRpcHistory = jsonRpcHistory - setUpPublishers() - } - - func request(_ wcMethod: WCMethod, onTopic topic: String) async throws { - try await request(topic: topic, payload: wcMethod.asRequest()) - } - - /// Completes when networking client sends a request - func request(topic: String, payload: WCRequest) async throws { - try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) - let message = try serializer.serialize(topic: topic, encodable: payload) - let prompt = shouldPrompt(payload.method) - try await relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) - } - - func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) { - let payload = wcMethod.asRequest() - do { - try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) - let message = try serializer.serialize(topic: topic, encodable: payload) - let prompt = shouldPrompt(payload.method) - relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) { [weak self] error in - guard let self = self else {return} - if let error = error { - self.logger.error(error) - } else { - var cancellable: AnyCancellable! - cancellable = self.responsePublisher - .filter {$0.result.id == payload.id} - .sink { (response) in - cancellable.cancel() - self.logger.debug("WC Relay - received response on topic: \(topic)") - switch response.result { - case .response(let response): - completion?(.success(response)) - case .error(let error): - self.logger.debug("Request error: \(error)") - completion?(.failure(error)) - } - } - } - } - } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { - logger.info("Info: Json Rpc Duplicate Detected") - } catch { - logger.error(error) - } - } - - /// Completes with an acknowledgement from the relay network. - /// completes with error if networking client was not able to send a message - func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void)) { - do { - let payload = wcMethod.asRequest() - try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) - let message = try serializer.serialize(topic: topic, encodable: payload) - let prompt = shouldPrompt(payload.method) - relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) { error in - completion(error) - } - } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { - logger.info("Info: Json Rpc Duplicate Detected") - } catch { - logger.error(error) - } - } - - func respond(topic: String, response: JsonRpcResult, tag: Int) async throws { - _ = try jsonRpcHistory.resolve(response: response) - - let message = try serializer.serialize(topic: topic, encodable: response.value) - logger.debug("Responding....topic: \(topic)") - - do { - try await relayClient.publish(topic: topic, payload: message, tag: tag, prompt: false) - } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { - logger.info("Info: Json Rpc Duplicate Detected") - } - } - - func respondSuccess(payload: WCRequestSubscriptionPayload) async throws { - let response = JSONRPCResponse(id: payload.wcRequest.id, result: AnyCodable(true)) - try await respond(topic: payload.topic, response: JsonRpcResult.response(response), tag: payload.wcRequest.responseTag) - } - - func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws { - let response = JSONRPCErrorResponse(id: payload.wcRequest.id, error: JSONRPCErrorResponse.Error(code: reason.code, message: reason.message)) - try await respond(topic: payload.topic, response: JsonRpcResult.error(response), tag: payload.wcRequest.responseTag) - } - - // TODO: Move to async - func respondSuccess(for payload: WCRequestSubscriptionPayload) { - Task(priority: .background) { - do { - try await respondSuccess(payload: payload) - } catch { - self.logger.error("Respond Success failed with: \(error.localizedDescription)") - } - } - } - - func subscribe(topic: String) async throws { - try await relayClient.subscribe(topic: topic) - } - - func unsubscribe(topic: String) { - relayClient.unsubscribe(topic: topic) { [weak self] error in - if let error = error { - self?.logger.error(error) - } else { - self?.jsonRpcHistory.delete(topic: topic) - } - } - } - - // MARK: - Private - - private func setUpPublishers() { - relayClient.socketConnectionStatusPublisher.sink { [weak self] status in - if status == .connected { - self?.transportConnectionPublisherSubject.send() - } - }.store(in: &publishers) - - relayClient.messagePublisher.sink { [weak self] (topic, message) in - self?.manageSubscription(topic, message) - } - .store(in: &publishers) - } - - private func manageSubscription(_ topic: String, _ encodedEnvelope: String) { - if let deserializedJsonRpcRequest: WCRequest = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { - handleWCRequest(topic: topic, request: deserializedJsonRpcRequest) - } else if let deserializedJsonRpcResponse: JSONRPCResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { - handleJsonRpcResponse(response: deserializedJsonRpcResponse) - } else if let deserializedJsonRpcError: JSONRPCErrorResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { - handleJsonRpcErrorResponse(response: deserializedJsonRpcError) - } else { - logger.warn("Warning: Networking Interactor - Received unknown object type from networking relay") - } - } - - private func handleWCRequest(topic: String, request: WCRequest) { - do { - try jsonRpcHistory.set(topic: topic, request: request, chainId: getChainId(request)) - let payload = WCRequestSubscriptionPayload(topic: topic, wcRequest: request) - wcRequestPublisherSubject.send(payload) - } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { - logger.info("Info: Json Rpc Duplicate Detected") - } catch { - logger.error(error) - } - } - - private func handleJsonRpcResponse(response: JSONRPCResponse) { - do { - let record = try jsonRpcHistory.resolve(response: JsonRpcResult.response(response)) - let wcResponse = WCResponse( - topic: record.topic, - chainId: record.chainId, - requestMethod: record.request.method, - requestParams: record.request.params, - result: JsonRpcResult.response(response)) - responsePublisherSubject.send(wcResponse) - } catch { - logger.info("Info: \(error.localizedDescription)") - } - } - - private func handleJsonRpcErrorResponse(response: JSONRPCErrorResponse) { - do { - let record = try jsonRpcHistory.resolve(response: JsonRpcResult.error(response)) - let wcResponse = WCResponse( - topic: record.topic, - chainId: record.chainId, - requestMethod: record.request.method, - requestParams: record.request.params, - result: JsonRpcResult.error(response)) - responsePublisherSubject.send(wcResponse) - } catch { - logger.info("Info: \(error.localizedDescription)") - } - } - - private func shouldPrompt(_ method: WCRequest.Method) -> Bool { - switch method { - case .sessionRequest: - return true - default: - return false - } - } - - func getChainId(_ request: WCRequest) -> String? { - guard case let .sessionRequest(payload) = request.params else {return nil} - return payload.chainId.absoluteString - } -} +//import Foundation +//import Combine +//import WalletConnectUtils +//import WalletConnectKMS +// +//protocol NetworkInteracting: AnyObject { +// var transportConnectionPublisher: AnyPublisher {get} +// var wcRequestPublisher: AnyPublisher {get} +// var responsePublisher: AnyPublisher {get} +// /// Completes when request sent from a networking client +// func request(_ wcMethod: WCMethod, onTopic topic: String) async throws +// /// Completes with an acknowledgement from the relay network +// func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void)) +// /// Completes with a peer response +// func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) +// func respond(topic: String, response: JsonRpcResult, tag: Int) async throws +// func respondSuccess(payload: WCRequestSubscriptionPayload) async throws +// func respondSuccess(for payload: WCRequestSubscriptionPayload) +// func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws +// func subscribe(topic: String) async throws +// func unsubscribe(topic: String) +//} +// +//extension NetworkInteracting { +// func request(_ wcMethod: WCMethod, onTopic topic: String) { +// requestPeerResponse(wcMethod, onTopic: topic, completion: nil) +// } +//} +// +//class NetworkInteractor: NetworkInteracting { +// +// private var publishers = Set() +// +// private var relayClient: NetworkRelaying +// private let serializer: Serializing +// private let jsonRpcHistory: JsonRpcHistoryRecording +// +// private let transportConnectionPublisherSubject = PassthroughSubject() +// private let responsePublisherSubject = PassthroughSubject() +// private let wcRequestPublisherSubject = PassthroughSubject() +// +// var transportConnectionPublisher: AnyPublisher { +// transportConnectionPublisherSubject.eraseToAnyPublisher() +// } +// var wcRequestPublisher: AnyPublisher { +// wcRequestPublisherSubject.eraseToAnyPublisher() +// } +// var responsePublisher: AnyPublisher { +// responsePublisherSubject.eraseToAnyPublisher() +// } +// +// let logger: ConsoleLogging +// +// init(relayClient: NetworkRelaying, +// serializer: Serializing, +// logger: ConsoleLogging, +// jsonRpcHistory: JsonRpcHistoryRecording) { +// self.relayClient = relayClient +// self.serializer = serializer +// self.logger = logger +// self.jsonRpcHistory = jsonRpcHistory +// setUpPublishers() +// } +// +// func request(_ wcMethod: WCMethod, onTopic topic: String) async throws { +// try await request(topic: topic, payload: wcMethod.asRequest()) +// } +// +// /// Completes when networking client sends a request +// func request(topic: String, payload: WCRequest) async throws { +// try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) +// let message = try serializer.serialize(topic: topic, encodable: payload) +// let prompt = shouldPrompt(payload.method) +// try await relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) +// } +// +// func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) { +// let payload = wcMethod.asRequest() +// do { +// try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) +// let message = try serializer.serialize(topic: topic, encodable: payload) +// let prompt = shouldPrompt(payload.method) +// relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) { [weak self] error in +// guard let self = self else {return} +// if let error = error { +// self.logger.error(error) +// } else { +// var cancellable: AnyCancellable! +// cancellable = self.responsePublisher +// .filter {$0.result.id == payload.id} +// .sink { (response) in +// cancellable.cancel() +// self.logger.debug("WC Relay - received response on topic: \(topic)") +// switch response.result { +// case .response(let response): +// completion?(.success(response)) +// case .error(let error): +// self.logger.debug("Request error: \(error)") +// completion?(.failure(error)) +// } +// } +// } +// } +// } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { +// logger.info("Info: Json Rpc Duplicate Detected") +// } catch { +// logger.error(error) +// } +// } +// +// /// Completes with an acknowledgement from the relay network. +// /// completes with error if networking client was not able to send a message +// func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void)) { +// do { +// let payload = wcMethod.asRequest() +// try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) +// let message = try serializer.serialize(topic: topic, encodable: payload) +// let prompt = shouldPrompt(payload.method) +// relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) { error in +// completion(error) +// } +// } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { +// logger.info("Info: Json Rpc Duplicate Detected") +// } catch { +// logger.error(error) +// } +// } +// +// func respond(topic: String, response: JsonRpcResult, tag: Int) async throws { +// _ = try jsonRpcHistory.resolve(response: response) +// +// let message = try serializer.serialize(topic: topic, encodable: response.value) +// logger.debug("Responding....topic: \(topic)") +// +// do { +// try await relayClient.publish(topic: topic, payload: message, tag: tag, prompt: false) +// } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { +// logger.info("Info: Json Rpc Duplicate Detected") +// } +// } +// +// func respondSuccess(payload: WCRequestSubscriptionPayload) async throws { +// let response = JSONRPCResponse(id: payload.wcRequest.id, result: AnyCodable(true)) +// try await respond(topic: payload.topic, response: JsonRpcResult.response(response), tag: payload.wcRequest.responseTag) +// } +// +// func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws { +// let response = JSONRPCErrorResponse(id: payload.wcRequest.id, error: JSONRPCErrorResponse.Error(code: reason.code, message: reason.message)) +// try await respond(topic: payload.topic, response: JsonRpcResult.error(response), tag: payload.wcRequest.responseTag) +// } +// +// // TODO: Move to async +// func respondSuccess(for payload: WCRequestSubscriptionPayload) { +// Task(priority: .background) { +// do { +// try await respondSuccess(payload: payload) +// } catch { +// self.logger.error("Respond Success failed with: \(error.localizedDescription)") +// } +// } +// } +// +// func subscribe(topic: String) async throws { +// try await relayClient.subscribe(topic: topic) +// } +// +// func unsubscribe(topic: String) { +// relayClient.unsubscribe(topic: topic) { [weak self] error in +// if let error = error { +// self?.logger.error(error) +// } else { +// self?.jsonRpcHistory.delete(topic: topic) +// } +// } +// } +// +// // MARK: - Private +// +// private func setUpPublishers() { +// relayClient.socketConnectionStatusPublisher.sink { [weak self] status in +// if status == .connected { +// self?.transportConnectionPublisherSubject.send() +// } +// }.store(in: &publishers) +// +// relayClient.messagePublisher.sink { [weak self] (topic, message) in +// self?.manageSubscription(topic, message) +// } +// .store(in: &publishers) +// } +// +// private func manageSubscription(_ topic: String, _ encodedEnvelope: String) { +// if let deserializedJsonRpcRequest: WCRequest = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { +// handleWCRequest(topic: topic, request: deserializedJsonRpcRequest) +// } else if let deserializedJsonRpcResponse: JSONRPCResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { +// handleJsonRpcResponse(response: deserializedJsonRpcResponse) +// } else if let deserializedJsonRpcError: JSONRPCErrorResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { +// handleJsonRpcErrorResponse(response: deserializedJsonRpcError) +// } else { +// logger.warn("Warning: Networking Interactor - Received unknown object type from networking relay") +// } +// } +// +// private func handleWCRequest(topic: String, request: WCRequest) { +// do { +// try jsonRpcHistory.set(topic: topic, request: request, chainId: getChainId(request)) +// let payload = WCRequestSubscriptionPayload(topic: topic, wcRequest: request) +// wcRequestPublisherSubject.send(payload) +// } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { +// logger.info("Info: Json Rpc Duplicate Detected") +// } catch { +// logger.error(error) +// } +// } +// +// private func handleJsonRpcResponse(response: JSONRPCResponse) { +// do { +// let record = try jsonRpcHistory.resolve(response: JsonRpcResult.response(response)) +// let wcResponse = WCResponse( +// topic: record.topic, +// chainId: record.chainId, +// requestMethod: record.request.method, +// requestParams: record.request.params, +// result: JsonRpcResult.response(response)) +// responsePublisherSubject.send(wcResponse) +// } catch { +// logger.info("Info: \(error.localizedDescription)") +// } +// } +// +// private func handleJsonRpcErrorResponse(response: JSONRPCErrorResponse) { +// do { +// let record = try jsonRpcHistory.resolve(response: JsonRpcResult.error(response)) +// let wcResponse = WCResponse( +// topic: record.topic, +// chainId: record.chainId, +// requestMethod: record.request.method, +// requestParams: record.request.params, +// result: JsonRpcResult.error(response)) +// responsePublisherSubject.send(wcResponse) +// } catch { +// logger.info("Info: \(error.localizedDescription)") +// } +// } +// +// private func shouldPrompt(_ method: WCRequest.Method) -> Bool { +// switch method { +// case .sessionRequest: +// return true +// default: +// return false +// } +// } +// +// func getChainId(_ request: WCRequest) -> String? { +// guard case let .sessionRequest(payload) = request.params else {return nil} +// return payload.chainId.absoluteString +// } +//} diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift index 5574cf826..fec9c6ed8 100644 --- a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift +++ b/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift @@ -1,19 +1,19 @@ -import Foundation -import WalletConnectRelay -import Combine - -extension RelayClient: NetworkRelaying {} - -protocol NetworkRelaying { - var messagePublisher: AnyPublisher<(topic: String, message: String), Never> { get } - var socketConnectionStatusPublisher: AnyPublisher { get } - func connect() throws - func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws - func publish(topic: String, payload: String, tag: Int, prompt: Bool) async throws - /// - returns: request id - func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) - func subscribe(topic: String, completion: @escaping (Error?) -> Void) - func subscribe(topic: String) async throws - /// - returns: request id - func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) -} +//import Foundation +//import WalletConnectRelay +//import Combine +// +//extension RelayClient: NetworkRelaying {} +// +//protocol NetworkRelaying { +// var messagePublisher: AnyPublisher<(topic: String, message: String), Never> { get } +// var socketConnectionStatusPublisher: AnyPublisher { get } +// func connect() throws +// func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws +// func publish(topic: String, payload: String, tag: Int, prompt: Bool) async throws +// /// - returns: request id +// func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) +// func subscribe(topic: String, completion: @escaping (Error?) -> Void) +// func subscribe(topic: String) async throws +// /// - returns: request id +// func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) +//} diff --git a/Sources/WalletConnectSign/Reason.swift b/Sources/WalletConnectSign/Reason.swift deleted file mode 100644 index d49aab825..000000000 --- a/Sources/WalletConnectSign/Reason.swift +++ /dev/null @@ -1,11 +0,0 @@ -// TODO: Refactor into codes. Reference: https://docs.walletconnect.com/2.0/protocol/reason-codes -public struct Reason { - - public let code: Int - public let message: String - - public init(code: Int, message: String) { - self.code = code - self.message = message - } -} diff --git a/Sources/WalletConnectSign/Request.swift b/Sources/WalletConnectSign/Request.swift index da55722b2..5cca66797 100644 --- a/Sources/WalletConnectSign/Request.swift +++ b/Sources/WalletConnectSign/Request.swift @@ -1,14 +1,15 @@ import Foundation +import JSONRPC import WalletConnectUtils public struct Request: Codable, Equatable { - public let id: Int64 + public let id: RPCID public let topic: String public let method: String public let params: AnyCodable public let chainId: Blockchain - internal init(id: Int64, topic: String, method: String, params: AnyCodable, chainId: Blockchain) { + internal init(id: RPCID, topic: String, method: String, params: AnyCodable, chainId: Blockchain) { self.id = id self.topic = topic self.method = method @@ -17,10 +18,10 @@ public struct Request: Codable, Equatable { } public init(topic: String, method: String, params: AnyCodable, chainId: Blockchain) { - self.init(id: JsonRpcID.generate(), topic: topic, method: method, params: params, chainId: chainId) + self.init(id: RPCID(JsonRpcID.generate()), topic: topic, method: method, params: params, chainId: chainId) } - internal init(id: Int64, topic: String, method: String, params: C, chainId: Blockchain) where C: Codable { + internal init(id: RPCID, topic: String, method: String, params: C, chainId: Blockchain) where C: Codable { self.init(id: id, topic: topic, method: method, params: AnyCodable(params), chainId: chainId) } } diff --git a/Sources/WalletConnectSign/Subscription/WCRequestSubscriptionPayload.swift b/Sources/WalletConnectSign/Subscription/WCRequestSubscriptionPayload.swift deleted file mode 100644 index 91efa9654..000000000 --- a/Sources/WalletConnectSign/Subscription/WCRequestSubscriptionPayload.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation -import WalletConnectUtils - -struct WCRequestSubscriptionPayload: Codable { - let topic: String - let wcRequest: WCRequest - - var timestamp: Date { - return JsonRpcID.timestamp(from: wcRequest.id) - } -} diff --git a/Sources/WalletConnectSign/Types/ReasonCode.swift b/Sources/WalletConnectSign/Types/ReasonCode.swift index cdc4f8db5..8ca23bd4c 100644 --- a/Sources/WalletConnectSign/Types/ReasonCode.swift +++ b/Sources/WalletConnectSign/Types/ReasonCode.swift @@ -1,4 +1,6 @@ -enum ReasonCode: Codable, Equatable { +import WalletConnectNetworking + +enum ReasonCode: Reason, Codable, Equatable { enum Context: String, Codable { case pairing = "pairing" diff --git a/Sources/WalletConnectSign/Types/WCMethod.swift b/Sources/WalletConnectSign/Types/WCMethod.swift deleted file mode 100644 index e2002188a..000000000 --- a/Sources/WalletConnectSign/Types/WCMethod.swift +++ /dev/null @@ -1,37 +0,0 @@ -import Foundation -import WalletConnectPairing - -enum WCMethod { - case wcPairingPing - case wcSessionPropose(SessionType.ProposeParams) - case wcSessionSettle(SessionType.SettleParams) - case wcSessionUpdate(SessionType.UpdateParams) - case wcSessionExtend(SessionType.UpdateExpiryParams) - case wcSessionDelete(SessionType.DeleteParams) - case wcSessionRequest(SessionType.RequestParams) - case wcSessionPing - case wcSessionEvent(SessionType.EventParams) - - func asRequest() -> WCRequest { - switch self { - case .wcPairingPing: - return WCRequest(method: .pairingPing, params: .pairingPing(PairingType.PingParams())) - case .wcSessionPropose(let proposalParams): - return WCRequest(method: .sessionPropose, params: .sessionPropose(proposalParams)) - case .wcSessionSettle(let settleParams): - return WCRequest(method: .sessionSettle, params: .sessionSettle(settleParams)) - case .wcSessionUpdate(let updateParams): - return WCRequest(method: .sessionUpdate, params: .sessionUpdate(updateParams)) - case .wcSessionExtend(let updateExpiryParams): - return WCRequest(method: .sessionExtend, params: .sessionExtend(updateExpiryParams)) - case .wcSessionDelete(let deleteParams): - return WCRequest(method: .sessionDelete, params: .sessionDelete(deleteParams)) - case .wcSessionRequest(let payloadParams): - return WCRequest(method: .sessionRequest, params: .sessionRequest(payloadParams)) - case .wcSessionPing: - return WCRequest(method: .sessionPing, params: .sessionPing(SessionType.PingParams())) - case .wcSessionEvent(let eventParams): - return WCRequest(method: .sessionEvent, params: .sessionEvent(eventParams)) - } - } -} diff --git a/Sources/WalletConnectSign/Types/WCRequest.swift b/Sources/WalletConnectSign/Types/WCRequest.swift index ec9edfdf8..a4a7e44df 100644 --- a/Sources/WalletConnectSign/Types/WCRequest.swift +++ b/Sources/WalletConnectSign/Types/WCRequest.swift @@ -1,179 +1,85 @@ import Foundation +import JSONRPC import WalletConnectPairing import WalletConnectUtils +import WalletConnectNetworking -struct WCRequest: Codable { - let id: Int64 - let jsonrpc: String - let method: Method - let params: Params +enum WCRequest: Codable { + case pairingDelete(PairingType.DeleteParams) + case pairingPing(PairingType.PingParams) + case sessionPropose(SessionType.ProposeParams) + case sessionSettle(SessionType.SettleParams) + case sessionUpdate(SessionType.UpdateParams) + case sessionExtend(SessionType.UpdateExpiryParams) + case sessionDelete(SessionType.DeleteParams) + case sessionRequest(SessionType.RequestParams) + case sessionPing(SessionType.PingParams) + case sessionEvent(SessionType.EventParams) - enum CodingKeys: CodingKey { - case id - case jsonrpc - case method - case params - } - - internal init(id: Int64 = JsonRpcID.generate(), jsonrpc: String = "2.0", method: Method, params: Params) { - self.id = id - self.jsonrpc = jsonrpc - self.method = method - self.params = params - } + enum Method: ProtocolMethod { + case pairingDelete + case pairingPing + case sessionPropose + case sessionSettle + case sessionUpdate + case sessionExtend + case sessionDelete + case sessionRequest + case sessionPing + case sessionEvent - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(Int64.self, forKey: .id) - jsonrpc = try container.decode(String.self, forKey: .jsonrpc) - method = try container.decode(Method.self, forKey: .method) - switch method { - case .pairingDelete: - let paramsValue = try container.decode(PairingType.DeleteParams.self, forKey: .params) - params = .pairingDelete(paramsValue) - case .pairingPing: - let paramsValue = try container.decode(PairingType.PingParams.self, forKey: .params) - params = .pairingPing(paramsValue) - case .sessionPropose: - let paramsValue = try container.decode(SessionType.ProposeParams.self, forKey: .params) - params = .sessionPropose(paramsValue) - case .sessionSettle: - let paramsValue = try container.decode(SessionType.SettleParams.self, forKey: .params) - params = .sessionSettle(paramsValue) - case .sessionUpdate: - let paramsValue = try container.decode(SessionType.UpdateParams.self, forKey: .params) - params = .sessionUpdate(paramsValue) - case .sessionDelete: - let paramsValue = try container.decode(SessionType.DeleteParams.self, forKey: .params) - params = .sessionDelete(paramsValue) - case .sessionRequest: - let paramsValue = try container.decode(SessionType.RequestParams.self, forKey: .params) - params = .sessionRequest(paramsValue) - case .sessionPing: - let paramsValue = try container.decode(SessionType.PingParams.self, forKey: .params) - params = .sessionPing(paramsValue) - case .sessionExtend: - let paramsValue = try container.decode(SessionType.UpdateExpiryParams.self, forKey: .params) - params = .sessionExtend(paramsValue) - case .sessionEvent: - let paramsValue = try container.decode(SessionType.EventParams.self, forKey: .params) - params = .sessionEvent(paramsValue) - } - } - - func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(id, forKey: .id) - try container.encode(jsonrpc, forKey: .jsonrpc) - try container.encode(method.rawValue, forKey: .method) - switch params { - case .pairingDelete(let params): - try container.encode(params, forKey: .params) - case .pairingPing(let params): - try container.encode(params, forKey: .params) - case .sessionPropose(let params): - try container.encode(params, forKey: .params) - case .sessionSettle(let params): - try container.encode(params, forKey: .params) - case .sessionUpdate(let params): - try container.encode(params, forKey: .params) - case .sessionExtend(let params): - try container.encode(params, forKey: .params) - case .sessionDelete(let params): - try container.encode(params, forKey: .params) - case .sessionRequest(let params): - try container.encode(params, forKey: .params) - case .sessionPing(let params): - try container.encode(params, forKey: .params) - case .sessionEvent(let params): - try container.encode(params, forKey: .params) + var method: String { + switch self { + case .pairingDelete: + return "wc_pairingDelete" + case .pairingPing: + return "wc_pairingPing" + case .sessionPropose: + return "wc_sessionPropose" + case .sessionSettle: + return "wc_sessionSettle" + case .sessionUpdate: + return "wc_sessionUpdate" + case .sessionExtend: + return "wc_sessionExtend" + case .sessionDelete: + return "wc_sessionDelete" + case .sessionRequest: + return "wc_sessionRequest" + case .sessionPing: + return "wc_sessionPing" + case .sessionEvent: + return "wc_sessionEvent" + } } - } -} -extension WCRequest { - enum Method: String, Codable { - case pairingDelete = "wc_pairingDelete" - case pairingPing = "wc_pairingPing" - case sessionPropose = "wc_sessionPropose" - case sessionSettle = "wc_sessionSettle" - case sessionUpdate = "wc_sessionUpdate" - case sessionExtend = "wc_sessionExtend" - case sessionDelete = "wc_sessionDelete" - case sessionRequest = "wc_sessionRequest" - case sessionPing = "wc_sessionPing" - case sessionEvent = "wc_sessionEvent" - } -} - -extension WCRequest { - enum Params: Codable, Equatable { - case pairingDelete(PairingType.DeleteParams) - case pairingPing(PairingType.PingParams) - case sessionPropose(SessionType.ProposeParams) - case sessionSettle(SessionType.SettleParams) - case sessionUpdate(SessionType.UpdateParams) - case sessionExtend(SessionType.UpdateExpiryParams) - case sessionDelete(SessionType.DeleteParams) - case sessionRequest(SessionType.RequestParams) - case sessionPing(SessionType.PingParams) - case sessionEvent(SessionType.EventParams) - - static func == (lhs: Params, rhs: Params) -> Bool { - switch (lhs, rhs) { - case (.pairingDelete(let lhsParam), pairingDelete(let rhsParam)): - return lhsParam == rhsParam - case (.sessionPropose(let lhsParam), sessionPropose(let rhsParam)): - return lhsParam == rhsParam - case (.sessionSettle(let lhsParam), sessionSettle(let rhsParam)): - return lhsParam == rhsParam - case (.sessionUpdate(let lhsParam), sessionUpdate(let rhsParam)): - return lhsParam == rhsParam - case (.sessionExtend(let lhsParam), sessionExtend(let rhsParams)): - return lhsParam == rhsParams - case (.sessionDelete(let lhsParam), sessionDelete(let rhsParam)): - return lhsParam == rhsParam - case (.sessionRequest(let lhsParam), sessionRequest(let rhsParam)): - return lhsParam == rhsParam - case (.sessionPing(let lhsParam), sessionPing(let rhsParam)): - return lhsParam == rhsParam - case (.sessionEvent(let lhsParam), sessionEvent(let rhsParam)): - return lhsParam == rhsParam - default: - return false + var requestTag: Int { + switch self { + case .pairingDelete: + return 1000 + case .pairingPing: + return 1002 + case .sessionPropose: + return 1100 + case .sessionSettle: + return 1102 + case .sessionUpdate: + return 1104 + case .sessionExtend: + return 1106 + case .sessionDelete: + return 1112 + case .sessionRequest: + return 1108 + case .sessionPing: + return 1114 + case .sessionEvent: + return 1110 } } - } -} - -extension WCRequest { - var tag: Int { - switch method { - case .pairingDelete: - return 1000 - case .pairingPing: - return 1002 - case .sessionPropose: - return 1100 - case .sessionSettle: - return 1102 - case .sessionUpdate: - return 1104 - case .sessionExtend: - return 1106 - case .sessionDelete: - return 1112 - case .sessionRequest: - return 1108 - case .sessionPing: - return 1114 - case .sessionEvent: - return 1110 + var responseTag: Int { + return requestTag + 1 } } - - var responseTag: Int { - return tag + 1 - } } diff --git a/Sources/WalletConnectSign/Types/WCResponse.swift b/Sources/WalletConnectSign/Types/WCResponse.swift deleted file mode 100644 index b457b9ed0..000000000 --- a/Sources/WalletConnectSign/Types/WCResponse.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation -import WalletConnectUtils - -struct WCResponse: Codable { - let topic: String - let chainId: String? - let requestMethod: WCRequest.Method - let requestParams: WCRequest.Params - let result: JsonRpcResult - - var timestamp: Date { - return JsonRpcID.timestamp(from: result.id) - } -} From a67e09dfccde79224b771d77b8a425781bf387a2 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 7 Sep 2022 00:02:10 +0300 Subject: [PATCH 05/40] ApproveEngine --- .../Engine/Common/ApproveEngine.swift | 107 +++++++++--------- .../Engine/Common/PairingEngine.swift | 1 + .../Engine/Common/SessionEngine.swift | 1 - .../Engine/Controller/PairEngine.swift | 1 + .../NetworkingInteractorMock.swift | 16 ++- 5 files changed, 69 insertions(+), 57 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index a5a310a1d..e75cc8bde 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -1,5 +1,6 @@ import Foundation import Combine +import JSONRPC import WalletConnectUtils import WalletConnectKMS import WalletConnectPairing @@ -12,7 +13,6 @@ final class ApproveEngine { case proposalPayloadsNotFound case pairingNotFound case agreementMissingOrInvalid - case respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) } var onSessionProposal: ((Session.Proposal) -> Void)? @@ -24,7 +24,7 @@ final class ApproveEngine { private let networkingInteractor: NetworkInteracting private let pairingStore: WCPairingStorage private let sessionStore: WCSessionStorage - private let proposalPayloadsStore: CodableStore + private let proposalPayloadsStore: CodableStore> private let sessionToPairingTopic: CodableStore private let metadata: AppMetadata private let kms: KeyManagementServiceProtocol @@ -34,7 +34,7 @@ final class ApproveEngine { init( networkingInteractor: NetworkInteracting, - proposalPayloadsStore: CodableStore, + proposalPayloadsStore: CodableStore>, sessionToPairingTopic: CodableStore, metadata: AppMetadata, kms: KeyManagementServiceProtocol, @@ -57,7 +57,7 @@ final class ApproveEngine { func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace]) async throws { let payload = try proposalPayloadsStore.get(key: proposerPubKey) - guard let payload = payload, case .sessionPropose(let proposal) = payload.wcRequest.params else { + guard let payload = payload, case .sessionPropose(let proposal) = payload.request else { throw Errors.wrongRequestParams } @@ -80,14 +80,13 @@ final class ApproveEngine { throw Errors.relayNotFound } - let proposeResponse = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation) - let response = JSONRPCResponse(id: payload.wcRequest.id, result: AnyCodable(proposeResponse)) - guard var pairing = pairingStore.getPairing(forTopic: payload.topic) else { throw Errors.pairingNotFound } - try await networkingInteractor.respond(topic: payload.topic, response: .response(response), tag: payload.wcRequest.responseTag) + let result = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation) + let response = RPCResponse(id: payload.id, result: result) + try await networkingInteractor.respond(topic: payload.topic, response: response, tag: WCRequest.Method.sessionPropose.responseTag) try pairing.updateExpiry() pairingStore.setPairing(pairing) @@ -100,7 +99,7 @@ final class ApproveEngine { throw Errors.proposalPayloadsNotFound } proposalPayloadsStore.delete(forKey: proposerPubKey) - try await networkingInteractor.respondError(payload: payload, reason: reason) + try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: WCRequest.Method.sessionPropose.responseTag, reason: reason) // TODO: Delete pairing if inactive } @@ -141,7 +140,8 @@ final class ApproveEngine { try await networkingInteractor.subscribe(topic: topic) sessionStore.setSession(session) - try await networkingInteractor.request(.wcSessionSettle(settleParams), onTopic: topic) + let request = RPCRequest(method: WCRequest.Method.sessionSettle.method, params: WCRequest.sessionSettle(settleParams)) + try await networkingInteractor.request(request, topic: topic, tag: WCRequest.Method.sessionSettle.requestTag) onSessionSettle?(session.publicRepresentation()) } } @@ -151,40 +151,34 @@ final class ApproveEngine { private extension ApproveEngine { func setupNetworkingSubscriptions() { - networkingInteractor.responsePublisher - .sink { [unowned self] response in - switch response.requestParams { + networkingInteractor.responseSubscription(on: nil) + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + switch payload.request { case .sessionPropose(let proposal): - handleSessionProposeResponse(response: response, proposal: proposal) + handleSessionProposeResponse(payload: payload, proposal: proposal) case .sessionSettle: - handleSessionSettleResponse(response: response) + handleSessionSettleResponse(payload: payload) default: break } }.store(in: &publishers) - networkingInteractor.wcRequestPublisher - .sink { [unowned self] subscriptionPayload in - do { - switch subscriptionPayload.wcRequest.params { - case .sessionPropose(let proposal): - try handleSessionProposeRequest(payload: subscriptionPayload, proposal: proposal) - case .sessionSettle(let settleParams): - try handleSessionSettleRequest(payload: subscriptionPayload, settleParams: settleParams) - default: return - } - } catch Errors.respondError(let payload, let reason) { - respondError(payload: payload, reason: reason) - } catch { - logger.error("Unexpected Error: \(error.localizedDescription)") + networkingInteractor.requestSubscription(on: nil) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + switch payload.request { + case .sessionPropose(let proposal): + handleSessionProposeRequest(payload: payload, proposal: proposal) + case .sessionSettle(let params): + handleSessionSettleRequest(payload: payload, params: params) + default: return } }.store(in: &publishers) } - func respondError(payload: SubscriptionPayload, reason: ReasonCode) { + func respondError(payload: SubscriptionPayload, reason: ReasonCode, tag: Int) { Task { do { - try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: <#T##Int#>, reason: reason) + try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: tag, reason: reason) } catch { logger.error("Respond Error failed with: \(error.localizedDescription)") } @@ -199,12 +193,12 @@ private extension ApproveEngine { // MARK: SessionProposeResponse // TODO: Move to Non-Controller SettleEngine - func handleSessionProposeResponse(response: WCResponse, proposal: SessionType.ProposeParams) { + func handleSessionProposeResponse(payload: ResponseSubscriptionPayload, proposal: SessionType.ProposeParams) { do { let sessionTopic = try handleProposeResponse( - pairingTopic: response.topic, + pairingTopic: payload.topic, proposal: proposal, - result: response.result + result: payload.response ) settlingProposal = proposal @@ -260,48 +254,55 @@ private extension ApproveEngine { // MARK: SessionSettleResponse - func handleSessionSettleResponse(response: WCResponse) { - guard let session = sessionStore.getSession(forTopic: response.topic) else { return } - switch response.result { + func handleSessionSettleResponse(payload: ResponseSubscriptionPayload) { + guard let session = sessionStore.getSession(forTopic: payload.topic) else { return } + switch payload.response { case .response: logger.debug("Received session settle response") - guard var session = sessionStore.getSession(forTopic: response.topic) else { return } + guard var session = sessionStore.getSession(forTopic: payload.topic) else { return } session.acknowledge() sessionStore.setSession(session) case .error(let error): logger.error("Error - session rejected, Reason: \(error)") - networkingInteractor.unsubscribe(topic: response.topic) - sessionStore.delete(topic: response.topic) - kms.deleteAgreementSecret(for: response.topic) + networkingInteractor.unsubscribe(topic: payload.topic) + sessionStore.delete(topic: payload.topic) + kms.deleteAgreementSecret(for: payload.topic) kms.deletePrivateKey(for: session.publicKey!) } } // MARK: SessionProposeRequest - func handleSessionProposeRequest(payload: WCRequestSubscriptionPayload, proposal: SessionType.ProposeParams) throws { + func handleSessionProposeRequest(payload: RequestSubscriptionPayload, proposal: SessionType.ProposeParams) { logger.debug("Received Session Proposal") - do { try Namespace.validate(proposal.requiredNamespaces) } catch { throw Errors.respondError(payload: payload, reason: .invalidUpdateRequest) } + do { try Namespace.validate(proposal.requiredNamespaces) } catch { + return respondError(payload: payload, reason: .invalidUpdateRequest, tag: WCRequest.Method.sessionPropose.responseTag) + } proposalPayloadsStore.set(payload, forKey: proposal.proposer.publicKey) onSessionProposal?(proposal.publicRepresentation()) } // MARK: SessionSettleRequest - func handleSessionSettleRequest(payload: WCRequestSubscriptionPayload, settleParams: SessionType.SettleParams) throws { + func handleSessionSettleRequest(payload: RequestSubscriptionPayload, params: SessionType.SettleParams) { logger.debug("Did receive session settle request") - guard let proposedNamespaces = settlingProposal?.requiredNamespaces - else { throw Errors.respondError(payload: payload, reason: .invalidUpdateRequest) } + let tag = WCRequest.Method.sessionSettle.responseTag + + guard let proposedNamespaces = settlingProposal?.requiredNamespaces else { + return respondError(payload: payload, reason: .invalidUpdateRequest, tag: tag) + } settlingProposal = nil - let sessionNamespaces = settleParams.namespaces + let sessionNamespaces = params.namespaces do { try Namespace.validate(sessionNamespaces) try Namespace.validateApproved(sessionNamespaces, against: proposedNamespaces) } catch WalletConnectError.unsupportedNamespace(let reason) { - throw Errors.respondError(payload: payload, reason: reason) + return respondError(payload: payload, reason: reason, tag: tag) + } catch { + return respondError(payload: payload, reason: .invalidUpdateRequest, tag: tag) } let topic = payload.topic @@ -311,20 +312,22 @@ private extension ApproveEngine { metadata: metadata ) if let pairingTopic = try? sessionToPairingTopic.get(key: topic) { - updatePairingMetadata(topic: pairingTopic, metadata: settleParams.controller.metadata) + updatePairingMetadata(topic: pairingTopic, metadata: params.controller.metadata) } let session = WCSession( topic: topic, timestamp: Date(), selfParticipant: selfParticipant, - peerParticipant: settleParams.controller, - settleParams: settleParams, + peerParticipant: params.controller, + settleParams: params, requiredNamespaces: proposedNamespaces, acknowledged: true ) sessionStore.setSession(session) - networkingInteractor.respondSuccess(for: payload) + Task(priority: .high) { + try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: tag) + } onSessionSettle?(session.publicRepresentation()) } } diff --git a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift index d9ec3ac3c..43a1b4b63 100644 --- a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift @@ -3,6 +3,7 @@ import Combine import WalletConnectPairing import WalletConnectUtils import WalletConnectKMS +import WalletConnectNetworking final class PairingEngine { diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index df321e01a..35934cf24 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -7,7 +7,6 @@ import WalletConnectNetworking final class SessionEngine { enum Errors: Error { - case respondError(payload: SubscriptionPayload, reason: ReasonCode) case sessionNotFound(topic: String) } diff --git a/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift b/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift index 9ad8956b7..81b4465d1 100644 --- a/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift +++ b/Sources/WalletConnectSign/Engine/Controller/PairEngine.swift @@ -1,6 +1,7 @@ import Foundation import WalletConnectKMS import WalletConnectPairing +import WalletConnectNetworking actor PairEngine { private let networkingInteractor: NetworkInteracting diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 10f42fc08..0ddc96adc 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -25,9 +25,13 @@ public class NetworkingInteractorMock: NetworkInteracting { responsePublisherSubject.eraseToAnyPublisher() } - public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { + // TODO: Avoid copy paste from NetworkInteractor + public func requestSubscription(on request: ProtocolMethod?) -> AnyPublisher, Never> { return requestPublisher - .filter { $0.request.method == request.method } + .filter { rpcRequest in + guard let request = request else { return true } + return rpcRequest.request.method == request.method + } .compactMap { topic, rpcRequest in guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil } return RequestSubscriptionPayload(id: id, topic: topic, request: request) @@ -35,9 +39,13 @@ public class NetworkingInteractorMock: NetworkInteracting { .eraseToAnyPublisher() } - public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { + // TODO: Avoid copy paste from NetworkInteractor + public func responseSubscription(on request: ProtocolMethod?) -> AnyPublisher, Never> { return responsePublisher - .filter { $0.request.method == request.method } + .filter { rpcRequest in + guard let request = request else { return true } + return rpcRequest.request.method == request.method + } .compactMap { topic, rpcRequest, rpcResponse in guard let id = rpcRequest.id, From bfcd3af60ad1b02fce4f8f19c0a0adb64344fa6a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 8 Sep 2022 04:00:32 +0300 Subject: [PATCH 06/40] Sign: Networking package imported --- .../Sign/SignClientTests.swift | 20 +-- .../Services/App/AppRespondSubscriber.swift | 2 +- Sources/JSONRPC/RPCID.swift | 10 ++ Sources/JSONRPC/RPCResponse.swift | 38 +++--- Sources/JSONRPC/RPCResult.swift | 39 ++++++ .../NetworkInteracting.swift | 8 +- .../NetworkInteractor.swift | 14 +- .../ResponseSubscriptionErrorPayload.swift | 8 +- Sources/WalletConnectRelay/RelayClient.swift | 4 +- .../Engine/Common/ApproveEngine.swift | 98 +++++++------- .../Engine/Common/DeletePairingService.swift | 5 +- .../Engine/Common/DeleteSessionService.swift | 5 +- .../Engine/Common/PairingEngine.swift | 65 +++++----- .../Engine/Common/SessionEngine.swift | 122 +++++++++--------- .../ControllerSessionStateMachine.swift | 52 ++++---- .../NonControllerSessionStateMachine.swift | 67 +++++----- .../JsonRpcHistory/JsonRpcHistory.swift | 63 --------- .../JsonRpcHistory/JsonRpcRecord.swift | 15 --- Sources/WalletConnectSign/Response.swift | 3 +- Sources/WalletConnectSign/Sign/Sign.swift | 6 +- .../WalletConnectSign/Sign/SignClient.swift | 34 ++--- .../Sign/SignClientFactory.swift | 9 +- .../Types/Session/SessionType.swift | 15 +-- .../Types/SignProtocolMethod.swift | 72 +++++++++++ .../WalletConnectSign/Types/WCRequest.swift | 85 ------------ .../JSONRPC/JSONRPCErrorResponse.swift | 27 ---- .../JSONRPC/JSONRPCRequest.swift | 23 ---- .../JSONRPC/JSONRPCResponse.swift | 18 --- .../JSONRPC/JsonRpcResult.swift | 24 ---- .../WalletConnectUtils/JsonRpcHistory.swift | 58 --------- .../WalletConnectUtils/JsonRpcRecord.swift | 27 ---- .../NetworkingInteractorMock.swift | 15 +-- 32 files changed, 426 insertions(+), 625 deletions(-) create mode 100644 Sources/JSONRPC/RPCResult.swift delete mode 100644 Sources/WalletConnectSign/JsonRpcHistory/JsonRpcHistory.swift delete mode 100644 Sources/WalletConnectSign/JsonRpcHistory/JsonRpcRecord.swift create mode 100644 Sources/WalletConnectSign/Types/SignProtocolMethod.swift delete mode 100644 Sources/WalletConnectSign/Types/WCRequest.swift delete mode 100644 Sources/WalletConnectUtils/JSONRPC/JSONRPCErrorResponse.swift delete mode 100644 Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift delete mode 100644 Sources/WalletConnectUtils/JSONRPC/JSONRPCResponse.swift delete mode 100644 Sources/WalletConnectUtils/JSONRPC/JsonRpcResult.swift delete mode 100644 Sources/WalletConnectUtils/JsonRpcHistory.swift delete mode 100644 Sources/WalletConnectUtils/JsonRpcRecord.swift diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index e00529b0b..3dd004d78 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -1,5 +1,6 @@ import XCTest import WalletConnectUtils +import JSONRPC @testable import WalletConnectKMS @testable import WalletConnectSign @testable import WalletConnectRelay @@ -171,7 +172,7 @@ final class SignClientTests: XCTestCase { } dapp.onSessionSettled = { [unowned self] settledSession in Task(priority: .high) { - let request = Request(id: 0, topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain) + let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain) try await dapp.client.request(params: request) } } @@ -181,14 +182,13 @@ final class SignClientTests: XCTestCase { XCTAssertEqual(sessionRequest.method, requestMethod) requestExpectation.fulfill() Task(priority: .high) { - let jsonrpcResponse = JSONRPCResponse(id: sessionRequest.id, result: AnyCodable(responseParams)) - try await wallet.client.respond(topic: sessionRequest.topic, response: .response(jsonrpcResponse)) + try await wallet.client.respond(topic: sessionRequest.topic, requestId: sessionRequest.id, response: .response(AnyCodable(responseParams))) } } dapp.onSessionResponse = { response in switch response.result { case .response(let response): - XCTAssertEqual(try! response.result.get(String.self), responseParams) + XCTAssertEqual(try! response.get(String.self), responseParams) case .error: XCTFail() } @@ -207,7 +207,8 @@ final class SignClientTests: XCTestCase { let requestMethod = "eth_sendTransaction" let requestParams = [EthSendTransaction.stub()] - let error = JSONRPCErrorResponse.Error(code: 0, message: "error") + let error = JSONRPCError(code: 0, message: "error") + let chain = Blockchain("eip155:1")! wallet.onSessionProposal = { [unowned self] proposal in @@ -217,22 +218,21 @@ final class SignClientTests: XCTestCase { } dapp.onSessionSettled = { [unowned self] settledSession in Task(priority: .high) { - let request = Request(id: 0, topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain) + let request = Request(id: RPCID(0), topic: settledSession.topic, method: requestMethod, params: requestParams, chainId: chain) try await dapp.client.request(params: request) } } wallet.onSessionRequest = { [unowned self] sessionRequest in Task(priority: .high) { - let response = JSONRPCErrorResponse(id: sessionRequest.id, error: error) - try await wallet.client.respond(topic: sessionRequest.topic, response: .error(response)) + try await wallet.client.respond(topic: sessionRequest.topic, requestId: sessionRequest.id, response: .error(error)) } } dapp.onSessionResponse = { response in switch response.result { case .response: XCTFail() - case .error(let errorResponse): - XCTAssertEqual(error, errorResponse.error) + case .error(let receivedError): + XCTAssertEqual(error, receivedError) } expectation.fulfill() } diff --git a/Sources/Auth/Services/App/AppRespondSubscriber.swift b/Sources/Auth/Services/App/AppRespondSubscriber.swift index 2b3b353fa..a0e9f8c40 100644 --- a/Sources/Auth/Services/App/AppRespondSubscriber.swift +++ b/Sources/Auth/Services/App/AppRespondSubscriber.swift @@ -33,7 +33,7 @@ class AppRespondSubscriber { private func subscribeForResponse() { networkingInteractor.responseErrorSubscription(on: AuthProtocolMethod.authRequest) - .sink { [unowned self] payload in + .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in guard let error = AuthError(code: payload.error.code) else { return } onResponse?(payload.id, .failure(error)) }.store(in: &publishers) diff --git a/Sources/JSONRPC/RPCID.swift b/Sources/JSONRPC/RPCID.swift index ff0ef3c83..21dcfb682 100644 --- a/Sources/JSONRPC/RPCID.swift +++ b/Sources/JSONRPC/RPCID.swift @@ -1,3 +1,4 @@ +import Foundation import Commons import Foundation @@ -15,3 +16,12 @@ struct IntIdentifierGenerator: IdentifierGenerator { return RPCID(timestamp + random) } } + +extension RPCID { + + public var timestamp: Date { + guard let id = self.right else { return .distantPast } + let interval = TimeInterval(id / 1000 / 1000) + return Date(timeIntervalSince1970: interval) + } +} diff --git a/Sources/JSONRPC/RPCResponse.swift b/Sources/JSONRPC/RPCResponse.swift index ad6ba9ca6..d38ef9c49 100644 --- a/Sources/JSONRPC/RPCResponse.swift +++ b/Sources/JSONRPC/RPCResponse.swift @@ -10,65 +10,65 @@ public struct RPCResponse: Equatable { public let id: RPCID? public var result: AnyCodable? { - if case .success(let value) = outcome { return value } + if case .response(let value) = outcome { return value } return nil } public var error: JSONRPCError? { - if case .failure(let error) = outcome { return error } + if case .error(let error) = outcome { return error } return nil } - public let outcome: Result + public let outcome: RPCResult - internal init(id: RPCID?, outcome: Result) { + internal init(id: RPCID?, outcome: RPCResult) { self.jsonrpc = "2.0" self.id = id self.outcome = outcome } public init(matchingRequest: RPCRequest, result: C) where C: Codable { - self.init(id: matchingRequest.id, outcome: .success(AnyCodable(result))) + self.init(id: matchingRequest.id, outcome: .response(AnyCodable(result))) } public init(matchingRequest: RPCRequest, error: JSONRPCError) { - self.init(id: matchingRequest.id, outcome: .failure(error)) + self.init(id: matchingRequest.id, outcome: .error(error)) } public init(id: Int64, result: C) where C: Codable { - self.init(id: RPCID(id), outcome: .success(AnyCodable(result))) + self.init(id: RPCID(id), outcome: .response(AnyCodable(result))) } public init(id: String, result: C) where C: Codable { - self.init(id: RPCID(id), outcome: .success(AnyCodable(result))) + self.init(id: RPCID(id), outcome: .response(AnyCodable(result))) } public init(id: RPCID, result: C) where C: Codable { - self.init(id: id, outcome: .success(AnyCodable(result))) + self.init(id: id, outcome: .response(AnyCodable(result))) } public init(id: RPCID?, error: JSONRPCError) { - self.init(id: id, outcome: .failure(error)) + self.init(id: id, outcome: .error(error)) } public init(id: Int64, error: JSONRPCError) { - self.init(id: RPCID(id), outcome: .failure(error)) + self.init(id: RPCID(id), outcome: .error(error)) } public init(id: String, error: JSONRPCError) { - self.init(id: RPCID(id), outcome: .failure(error)) + self.init(id: RPCID(id), outcome: .error(error)) } public init(id: Int64, errorCode: Int, message: String, associatedData: AnyCodable? = nil) { - self.init(id: RPCID(id), outcome: .failure(JSONRPCError(code: errorCode, message: message, data: associatedData))) + self.init(id: RPCID(id), outcome: .error(JSONRPCError(code: errorCode, message: message, data: associatedData))) } public init(id: String, errorCode: Int, message: String, associatedData: AnyCodable? = nil) { - self.init(id: RPCID(id), outcome: .failure(JSONRPCError(code: errorCode, message: message, data: associatedData))) + self.init(id: RPCID(id), outcome: .error(JSONRPCError(code: errorCode, message: message, data: associatedData))) } public init(errorWithoutID: JSONRPCError) { - self.init(id: nil, outcome: .failure(errorWithoutID)) + self.init(id: nil, outcome: .error(errorWithoutID)) } } @@ -104,9 +104,9 @@ extension RPCResponse: Codable { codingPath: [CodingKeys.result, CodingKeys.id], debugDescription: "A success response must have a valid `id`.")) } - outcome = .success(result) + outcome = .response(result) } else if let error = error { - outcome = .failure(error) + outcome = .error(error) } else { throw DecodingError.dataCorrupted(.init( codingPath: [CodingKeys.result, CodingKeys.error], @@ -119,9 +119,9 @@ extension RPCResponse: Codable { try container.encode(jsonrpc, forKey: .jsonrpc) try container.encode(id, forKey: .id) switch outcome { - case .success(let anyCodable): + case .response(let anyCodable): try container.encode(anyCodable, forKey: .result) - case .failure(let rpcError): + case .error(let rpcError): try container.encode(rpcError, forKey: .error) } } diff --git a/Sources/JSONRPC/RPCResult.swift b/Sources/JSONRPC/RPCResult.swift new file mode 100644 index 000000000..a3b81e094 --- /dev/null +++ b/Sources/JSONRPC/RPCResult.swift @@ -0,0 +1,39 @@ +import Foundation +import Commons + +public enum RPCResult: Codable, Equatable { + enum Errors: Error { + case decoding + } + + case response(AnyCodable) + case error(JSONRPCError) + + public var value: Codable { + switch self { + case .response(let value): + return value + case .error(let value): + return value + } + } + + public init(from decoder: Decoder) throws { + if let value = try? JSONRPCError(from: decoder) { + self = .error(value) + } else if let value = try? AnyCodable(from: decoder) { + self = .response(value) + } else { + throw Errors.decoding + } + } + + public func encode(to encoder: Encoder) throws { + switch self { + case .error(let value): + try value.encode(to: encoder) + case .response(let value): + try value.encode(to: encoder) + } + } +} diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 67d5e9673..85ca9177e 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -15,14 +15,16 @@ public protocol NetworkInteracting { func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws func requestSubscription( - on request: ProtocolMethod? + on request: ProtocolMethod ) -> AnyPublisher, Never> func responseSubscription( - on request: ProtocolMethod? + on request: ProtocolMethod ) -> AnyPublisher, Never> - func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher + func responseErrorSubscription( + on request: ProtocolMethod + ) -> AnyPublisher, Never> } extension NetworkInteracting { diff --git a/Sources/WalletConnectNetworking/NetworkInteractor.swift b/Sources/WalletConnectNetworking/NetworkInteractor.swift index f5ed34273..405533ef1 100644 --- a/Sources/WalletConnectNetworking/NetworkInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkInteractor.swift @@ -56,10 +56,9 @@ public class NetworkingInteractor: NetworkInteracting { } } - public func requestSubscription(on request: ProtocolMethod?) -> AnyPublisher, Never> { + public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return requestPublisher .filter { rpcRequest in - guard let request = request else { return true } return rpcRequest.request.method == request.method } .compactMap { topic, rpcRequest in @@ -69,10 +68,9 @@ public class NetworkingInteractor: NetworkInteracting { .eraseToAnyPublisher() } - public func responseSubscription(on request: ProtocolMethod?) -> AnyPublisher, Never> { + public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { rpcRequest in - guard let request = request else { return true } return rpcRequest.request.method == request.method } .compactMap { topic, rpcRequest, rpcResponse in @@ -85,12 +83,12 @@ public class NetworkingInteractor: NetworkInteracting { .eraseToAnyPublisher() } - public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher { + public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { $0.request.method == request.method } - .compactMap { (_, _, rpcResponse) in - guard let id = rpcResponse.id, let error = rpcResponse.error else { return nil } - return ResponseSubscriptionErrorPayload(id: id, error: error) + .compactMap { (topic, rpcRequest, rpcResponse) in + guard let id = rpcResponse.id, let request = try? rpcRequest.params?.get(Request.self), let error = rpcResponse.error else { return nil } + return ResponseSubscriptionErrorPayload(id: id, topic: topic, request: request, error: error) } .eraseToAnyPublisher() } diff --git a/Sources/WalletConnectNetworking/ResponseSubscriptionErrorPayload.swift b/Sources/WalletConnectNetworking/ResponseSubscriptionErrorPayload.swift index 8b38df244..589ebbcc2 100644 --- a/Sources/WalletConnectNetworking/ResponseSubscriptionErrorPayload.swift +++ b/Sources/WalletConnectNetworking/ResponseSubscriptionErrorPayload.swift @@ -1,12 +1,16 @@ import Foundation import JSONRPC -public struct ResponseSubscriptionErrorPayload { +public struct ResponseSubscriptionErrorPayload: Codable, SubscriptionPayload { public let id: RPCID + public let topic: String + public let request: Request public let error: JSONRPCError - public init(id: RPCID, error: JSONRPCError) { + public init(id: RPCID, topic: String, request: Request, error: JSONRPCError) { self.id = id + self.topic = topic + self.request = request self.error = error } } diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index 94ab8ceab..874bb6267 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -253,13 +253,13 @@ public final class RelayClient { } } else if let response = tryDecode(RPCResponse.self, from: payload) { switch response.outcome { - case .success(let anyCodable): + case .response(let anyCodable): if let _ = try? anyCodable.get(Bool.self) { // TODO: Handle success vs. error requestAcknowledgePublisherSubject.send(response.id) } else if let subscriptionId = try? anyCodable.get(String.self) { subscriptionResponsePublisherSubject.send((response.id, subscriptionId)) } - case .failure(let rpcError): + case .error(let rpcError): logger.error("Received RPC error from relay network: \(rpcError)") } } else { diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index e75cc8bde..e29813c63 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -16,7 +16,7 @@ final class ApproveEngine { } var onSessionProposal: ((Session.Proposal) -> Void)? - var onSessionRejected: ((Session.Proposal, SessionType.Reason) -> Void)? + var onSessionRejected: ((Session.Proposal, Reason) -> Void)? var onSessionSettle: ((Session) -> Void)? var settlingProposal: SessionProposal? @@ -24,7 +24,7 @@ final class ApproveEngine { private let networkingInteractor: NetworkInteracting private let pairingStore: WCPairingStorage private let sessionStore: WCSessionStorage - private let proposalPayloadsStore: CodableStore> + private let proposalPayloadsStore: CodableStore> private let sessionToPairingTopic: CodableStore private let metadata: AppMetadata private let kms: KeyManagementServiceProtocol @@ -34,7 +34,7 @@ final class ApproveEngine { init( networkingInteractor: NetworkInteracting, - proposalPayloadsStore: CodableStore>, + proposalPayloadsStore: CodableStore>, sessionToPairingTopic: CodableStore, metadata: AppMetadata, kms: KeyManagementServiceProtocol, @@ -51,16 +51,17 @@ final class ApproveEngine { self.pairingStore = pairingStore self.sessionStore = sessionStore - setupNetworkingSubscriptions() + setupRequestSubscriptions() + setupResponseSubscriptions() } func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace]) async throws { - let payload = try proposalPayloadsStore.get(key: proposerPubKey) - - guard let payload = payload, case .sessionPropose(let proposal) = payload.request else { + guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { throw Errors.wrongRequestParams } + let proposal = payload.request + proposalPayloadsStore.delete(forKey: proposerPubKey) try Namespace.validate(sessionNamespaces) @@ -86,7 +87,7 @@ final class ApproveEngine { let result = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation) let response = RPCResponse(id: payload.id, result: result) - try await networkingInteractor.respond(topic: payload.topic, response: response, tag: WCRequest.Method.sessionPropose.responseTag) + try await networkingInteractor.respond(topic: payload.topic, response: response, tag: SignProtocolMethod.sessionPropose.responseTag) try pairing.updateExpiry() pairingStore.setPairing(pairing) @@ -99,7 +100,7 @@ final class ApproveEngine { throw Errors.proposalPayloadsNotFound } proposalPayloadsStore.delete(forKey: proposerPubKey) - try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: WCRequest.Method.sessionPropose.responseTag, reason: reason) + try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: SignProtocolMethod.sessionPropose.responseTag, reason: reason) // TODO: Delete pairing if inactive } @@ -140,8 +141,8 @@ final class ApproveEngine { try await networkingInteractor.subscribe(topic: topic) sessionStore.setSession(session) - let request = RPCRequest(method: WCRequest.Method.sessionSettle.method, params: WCRequest.sessionSettle(settleParams)) - try await networkingInteractor.request(request, topic: topic, tag: WCRequest.Method.sessionSettle.requestTag) + let request = RPCRequest(method: SignProtocolMethod.sessionSettle.method, params: settleParams) + try await networkingInteractor.request(request, topic: topic, tag: SignProtocolMethod.sessionSettle.requestTag) onSessionSettle?(session.publicRepresentation()) } } @@ -150,28 +151,35 @@ final class ApproveEngine { private extension ApproveEngine { - func setupNetworkingSubscriptions() { - networkingInteractor.responseSubscription(on: nil) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in - switch payload.request { - case .sessionPropose(let proposal): - handleSessionProposeResponse(payload: payload, proposal: proposal) - case .sessionSettle: - handleSessionSettleResponse(payload: payload) - default: - break - } + func setupRequestSubscriptions() { + networkingInteractor.requestSubscription(on: SignProtocolMethod.sessionPropose) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + handleSessionProposeRequest(payload: payload) + }.store(in: &publishers) + + networkingInteractor.requestSubscription(on: SignProtocolMethod.sessionSettle) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + handleSessionSettleRequest(payload: payload) + }.store(in: &publishers) + } + + func setupResponseSubscriptions() { + networkingInteractor.responseSubscription(on: SignProtocolMethod.sessionPropose) + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + handleSessionProposeResponse(payload: payload) + }.store(in: &publishers) + + networkingInteractor.responseSubscription(on: SignProtocolMethod.sessionSettle) + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + handleSessionSettleResponse(payload: payload) }.store(in: &publishers) - networkingInteractor.requestSubscription(on: nil) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - switch payload.request { - case .sessionPropose(let proposal): - handleSessionProposeRequest(payload: payload, proposal: proposal) - case .sessionSettle(let params): - handleSessionSettleRequest(payload: payload, params: params) - default: return - } + networkingInteractor.responseErrorSubscription(on: SignProtocolMethod.sessionPropose) + .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in + onSessionRejected?( + payload.request.publicRepresentation(), + SessionType.Reason(code: payload.error.code, message: payload.error.message) + ) }.store(in: &publishers) } @@ -193,27 +201,27 @@ private extension ApproveEngine { // MARK: SessionProposeResponse // TODO: Move to Non-Controller SettleEngine - func handleSessionProposeResponse(payload: ResponseSubscriptionPayload, proposal: SessionType.ProposeParams) { + func handleSessionProposeResponse(payload: ResponseSubscriptionPayload) { do { let sessionTopic = try handleProposeResponse( pairingTopic: payload.topic, - proposal: proposal, + proposal: payload.request, result: payload.response ) - settlingProposal = proposal + settlingProposal = payload.request Task(priority: .high) { - try? await networkingInteractor.subscribe(topic: sessionTopic) + try await networkingInteractor.subscribe(topic: sessionTopic) } } catch { - guard let error = error as? JSONRPCErrorResponse else { + guard let error = error as? Reason else { return logger.debug(error.localizedDescription) } - onSessionRejected?(proposal.publicRepresentation(), SessionType.Reason(code: error.error.code, message: error.error.message)) + onSessionRejected?(payload.request.publicRepresentation(), SessionType.Reason(code: error.code, message: error.message)) } } - func handleProposeResponse(pairingTopic: String, proposal: SessionProposal, result: JsonRpcResult) throws -> String { + func handleProposeResponse(pairingTopic: String, proposal: SessionProposal, result: RPCResult) throws -> String { guard var pairing = pairingStore.getPairing(forTopic: pairingTopic) else { throw Errors.pairingNotFound } @@ -229,7 +237,7 @@ private extension ApproveEngine { pairingStore.setPairing(pairing) let selfPublicKey = try AgreementPublicKey(hex: proposal.proposer.publicKey) - let proposeResponse = try response.result.get(SessionType.ProposeResponse.self) + let proposeResponse = try response.get(SessionType.ProposeResponse.self) let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPublicKey, peerPublicKey: proposeResponse.responderPublicKey) let sessionTopic = agreementKeys.derivedTopic() @@ -254,7 +262,7 @@ private extension ApproveEngine { // MARK: SessionSettleResponse - func handleSessionSettleResponse(payload: ResponseSubscriptionPayload) { + func handleSessionSettleResponse(payload: ResponseSubscriptionPayload) { guard let session = sessionStore.getSession(forTopic: payload.topic) else { return } switch payload.response { case .response: @@ -273,20 +281,21 @@ private extension ApproveEngine { // MARK: SessionProposeRequest - func handleSessionProposeRequest(payload: RequestSubscriptionPayload, proposal: SessionType.ProposeParams) { + func handleSessionProposeRequest(payload: RequestSubscriptionPayload) { logger.debug("Received Session Proposal") + let proposal = payload.request do { try Namespace.validate(proposal.requiredNamespaces) } catch { - return respondError(payload: payload, reason: .invalidUpdateRequest, tag: WCRequest.Method.sessionPropose.responseTag) + return respondError(payload: payload, reason: .invalidUpdateRequest, tag: SignProtocolMethod.sessionPropose.responseTag) } proposalPayloadsStore.set(payload, forKey: proposal.proposer.publicKey) onSessionProposal?(proposal.publicRepresentation()) } // MARK: SessionSettleRequest - func handleSessionSettleRequest(payload: RequestSubscriptionPayload, params: SessionType.SettleParams) { + func handleSessionSettleRequest(payload: RequestSubscriptionPayload) { logger.debug("Did receive session settle request") - let tag = WCRequest.Method.sessionSettle.responseTag + let tag = SignProtocolMethod.sessionSettle.responseTag guard let proposedNamespaces = settlingProposal?.requiredNamespaces else { return respondError(payload: payload, reason: .invalidUpdateRequest, tag: tag) @@ -294,6 +303,7 @@ private extension ApproveEngine { settlingProposal = nil + let params = payload.request let sessionNamespaces = params.namespaces do { diff --git a/Sources/WalletConnectSign/Engine/Common/DeletePairingService.swift b/Sources/WalletConnectSign/Engine/Common/DeletePairingService.swift index a6324481e..2dbbede9d 100644 --- a/Sources/WalletConnectSign/Engine/Common/DeletePairingService.swift +++ b/Sources/WalletConnectSign/Engine/Common/DeletePairingService.swift @@ -1,7 +1,9 @@ import Foundation +import JSONRPC import WalletConnectKMS import WalletConnectUtils import WalletConnectPairing +import WalletConnectNetworking class DeletePairingService { private let networkingInteractor: NetworkInteracting @@ -23,7 +25,8 @@ class DeletePairingService { let reasonCode = ReasonCode.userDisconnected let reason = SessionType.Reason(code: reasonCode.code, message: reasonCode.message) logger.debug("Will delete pairing for reason: message: \(reason.message) code: \(reason.code)") - try await networkingInteractor.request(.wcSessionDelete(reason), onTopic: topic) + let request = RPCRequest(method: SignProtocolMethod.sessionDelete.method, params: reason) + try await networkingInteractor.request(request, topic: topic, tag: SignProtocolMethod.sessionDelete.requestTag) pairingStorage.delete(topic: topic) kms.deleteSymmetricKey(for: topic) networkingInteractor.unsubscribe(topic: topic) diff --git a/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift b/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift index 561af3507..f2ff1442d 100644 --- a/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift +++ b/Sources/WalletConnectSign/Engine/Common/DeleteSessionService.swift @@ -1,6 +1,8 @@ import Foundation +import JSONRPC import WalletConnectKMS import WalletConnectUtils +import WalletConnectNetworking class DeleteSessionService { private let networkingInteractor: NetworkInteracting @@ -22,7 +24,8 @@ class DeleteSessionService { let reasonCode = ReasonCode.userDisconnected let reason = SessionType.Reason(code: reasonCode.code, message: reasonCode.message) logger.debug("Will delete session for reason: message: \(reason.message) code: \(reason.code)") - try await networkingInteractor.request(.wcSessionDelete(reason), onTopic: topic) + let request = RPCRequest(method: SignProtocolMethod.sessionDelete.method, params: reason) + try await networkingInteractor.request(request, topic: topic, tag: SignProtocolMethod.sessionDelete.requestTag) sessionStore.delete(topic: topic) kms.deleteSymmetricKey(for: topic) networkingInteractor.unsubscribe(topic: topic) diff --git a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift index 43a1b4b63..0561e6446 100644 --- a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift @@ -1,5 +1,6 @@ import Foundation import Combine +import JSONRPC import WalletConnectPairing import WalletConnectUtils import WalletConnectKMS @@ -70,15 +71,9 @@ final class PairingEngine { relays: [relay], proposer: proposer, requiredNamespaces: namespaces) - return try await withCheckedThrowingContinuation { continuation in - networkingInteractor.requestNetworkAck(.wcSessionPropose(proposal), onTopic: pairingTopic) { error in - if let error = error { - continuation.resume(throwing: error) - } else { - continuation.resume() - } - } - } + + let request = RPCRequest(method: SignProtocolMethod.sessionPropose.method, params: proposal) + try await networkingInteractor.request(request, topic: pairingTopic, tag: SignProtocolMethod.sessionPropose.requestTag) } func ping(topic: String, completion: @escaping ((Result) -> Void)) { @@ -86,15 +81,16 @@ final class PairingEngine { logger.debug("Could not find pairing to ping for topic \(topic)") return } - networkingInteractor.requestPeerResponse(.wcPairingPing, onTopic: topic) { [unowned self] result in - switch result { - case .success: - logger.debug("Did receive ping response") - completion(.success(())) - case .failure(let error): - logger.debug("error: \(error)") - } - } +// TODO: Ping disabled +// networkingInteractor.requestPeerResponse(.wcPairingPing, onTopic: topic) { [unowned self] result in +// switch result { +// case .success: +// logger.debug("Did receive ping response") +// completion(.success(())) +// case .failure(let error): +// logger.debug("error: \(error)") +// } +// } } } @@ -103,26 +99,23 @@ final class PairingEngine { private extension PairingEngine { func setupNetworkingSubscriptions() { - networkingInteractor.transportConnectionPublisher - .sink { [unowned self] (_) in - let topics = pairingStore.getAll() - .map {$0.topic} - topics.forEach { topic in Task {try? await networkingInteractor.subscribe(topic: topic)}} - }.store(in: &publishers) + networkingInteractor.socketConnectionStatusPublisher + .sink { [unowned self] status in + guard status == .connected else { return } + pairingStore.getAll() + .forEach { pairing in + Task(priority: .high) { try await networkingInteractor.subscribe(topic: pairing.topic) } + } + } + .store(in: &publishers) - networkingInteractor.wcRequestPublisher - .sink { [unowned self] subscriptionPayload in - switch subscriptionPayload.wcRequest.params { - case .pairingPing: - wcPairingPing(subscriptionPayload) - default: - return + networkingInteractor.requestSubscription(on: SignProtocolMethod.pairingPing) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + Task(priority: .high) { + try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: SignProtocolMethod.pairingPing.responseTag) } - }.store(in: &publishers) - } - - func wcPairingPing(_ payload: WCRequestSubscriptionPayload) { - networkingInteractor.respondSuccess(for: payload) + } + .store(in: &publishers) } func setupExpirationHandling() { diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 35934cf24..0905d4f3d 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -33,7 +33,9 @@ final class SessionEngine { self.sessionStore = sessionStore self.logger = logger - setupNetworkingSubscriptions() + setupConnectionSubscriptions() + setupRequestSubscriptions() + setupResponseSubscriptions() setupExpirationSubscriptions() } @@ -74,17 +76,16 @@ final class SessionEngine { let chainRequest = SessionType.RequestParams.Request(method: request.method, params: request.params) let sessionRequestParams = SessionType.RequestParams(request: chainRequest, chainId: request.chainId) - let payload = WCRequest.sessionRequest(sessionRequestParams) - let rpcRequest = RPCRequest(method: WCRequest.Method.sessionRequest.method, params: payload) - try await networkingInteractor.request(rpcRequest, topic: request.topic, tag: WCRequest.Method.sessionRequest.requestTag) + let rpcRequest = RPCRequest(method: SignProtocolMethod.sessionRequest.method, params: sessionRequestParams) + try await networkingInteractor.request(rpcRequest, topic: request.topic, tag: SignProtocolMethod.sessionRequest.requestTag) } - func respondSessionRequest(topic: String, response: JsonRpcResult) async throws { + func respondSessionRequest(topic: String, requestId: RPCID, response: RPCResult) async throws { guard sessionStore.hasSession(forTopic: topic) else { throw Errors.sessionNotFound(topic: topic) } - // TODO: ??? -// try await networkingInteractor.respond(topic: topic, response: response, tag: 1109) // FIXME: Hardcoded tag + let response = RPCResponse(id: requestId, result: response) + try await networkingInteractor.respond(topic: topic, response: response, tag: 1109) // FIXME: Hardcoded tag } func emit(topic: String, event: SessionType.EventParams.Event, chainId: Blockchain) async throws { @@ -95,9 +96,8 @@ final class SessionEngine { guard session.hasPermission(forEvent: event.name, onChain: chainId) else { throw WalletConnectError.invalidEvent } - let params = SessionType.EventParams(event: event, chainId: chainId) - let rpcRequest = RPCRequest(method: WCRequest.Method.sessionEvent.method, params: params) - try await networkingInteractor.request(rpcRequest, topic: topic, tag: WCRequest.Method.sessionEvent.requestTag) + let rpcRequest = RPCRequest(method: SignProtocolMethod.sessionEvent.method, params: SessionType.EventParams(event: event, chainId: chainId)) + try await networkingInteractor.request(rpcRequest, topic: topic, tag: SignProtocolMethod.sessionEvent.requestTag) } } @@ -105,7 +105,7 @@ final class SessionEngine { private extension SessionEngine { - func setupNetworkingSubscriptions() { + func setupConnectionSubscriptions() { networkingInteractor.socketConnectionStatusPublisher .sink { [unowned self] status in guard status == .connected else { return } @@ -115,38 +115,49 @@ private extension SessionEngine { } } .store(in: &publishers) + } - networkingInteractor.requestSubscription(on: nil) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - switch payload.request { - case .sessionDelete(let params): - onSessionDelete(payload, params: params) - case .sessionRequest(let params): - onSessionRequest(payload, params: params) - case .sessionPing: - onSessionPing(payload) - case .sessionEvent(let params): - onSessionEvent(payload, params: params) - default: return - } - } - .store(in: &publishers) + func setupRequestSubscriptions() { + networkingInteractor.requestSubscription(on: SignProtocolMethod.sessionDelete) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + onSessionDelete(payload: payload) + }.store(in: &publishers) + + networkingInteractor.requestSubscription(on: SignProtocolMethod.sessionRequest) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + onSessionRequest(payload: payload) + }.store(in: &publishers) + + networkingInteractor.requestSubscription(on: SignProtocolMethod.sessionPing) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + onSessionPing(payload: payload) + }.store(in: &publishers) + + networkingInteractor.requestSubscription(on: SignProtocolMethod.sessionEvent) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + onSessionEvent(payload: payload) + }.store(in: &publishers) + } - networkingInteractor.responseSubscription(on: nil) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in - switch payload.request { - case .sessionRequest(let params): - // TODO: ??? Chain ID from request is ok? - // Need to check absolute string - let response = Response(topic: payload.topic, chainId: params.chainId.absoluteString, result: payload.response) - onSessionResponse?(response) - default: - break - } + func setupResponseSubscriptions() { + networkingInteractor.responseSubscription(on: SignProtocolMethod.sessionRequest) + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + onSessionResponse?(Response( + topic: payload.topic, + chainId: payload.request.chainId.absoluteString, + result: payload.response + )) } .store(in: &publishers) } + func setupExpirationSubscriptions() { + sessionStore.onSessionExpiration = { [weak self] session in + self?.kms.deletePrivateKey(for: session.selfParticipant.publicKey) + self?.kms.deleteAgreementSecret(for: session.topic) + } + } + func respondError(payload: SubscriptionPayload, reason: ReasonCode, tag: Int) { Task { do { @@ -157,8 +168,8 @@ private extension SessionEngine { } } - func onSessionDelete(_ payload: SubscriptionPayload, params: SessionType.DeleteParams) { - let tag = WCRequest.Method.sessionDelete.responseTag + func onSessionDelete(payload: RequestSubscriptionPayload) { + let tag = SignProtocolMethod.sessionDelete.responseTag let topic = payload.topic guard sessionStore.hasSession(forTopic: topic) else { return respondError(payload: payload, reason: .noSessionForTopic, tag: tag) @@ -168,18 +179,18 @@ private extension SessionEngine { Task(priority: .high) { try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: tag) } - onSessionDelete?(topic, params) + onSessionDelete?(topic, payload.request) } - func onSessionRequest(_ payload: SubscriptionPayload, params: SessionType.RequestParams) { - let tag = WCRequest.Method.sessionRequest.responseTag + func onSessionRequest(payload: RequestSubscriptionPayload) { + let tag = SignProtocolMethod.sessionRequest.responseTag let topic = payload.topic let request = Request( id: payload.id, topic: payload.topic, - method: WCRequest.Method.sessionRequest.method, - params: params, - chainId: params.chainId) + method: payload.request.request.method, + params: payload.request.request.params, + chainId: payload.request.chainId) guard let session = sessionStore.getSession(forTopic: topic) else { return respondError(payload: payload, reason: .noSessionForTopic, tag: tag) @@ -193,32 +204,25 @@ private extension SessionEngine { onSessionRequest?(request) } - func onSessionPing(_ payload: SubscriptionPayload) { + func onSessionPing(payload: SubscriptionPayload) { Task(priority: .high) { - try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: WCRequest.Method.sessionPing.responseTag) + try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: SignProtocolMethod.sessionPing.responseTag) } } - func onSessionEvent(_ payload: SubscriptionPayload, params: SessionType.EventParams) { - let tag = WCRequest.Method.sessionEvent.responseTag - let event = params.event + func onSessionEvent(payload: RequestSubscriptionPayload) { + let tag = SignProtocolMethod.sessionEvent.responseTag + let event = payload.request.event let topic = payload.topic guard let session = sessionStore.getSession(forTopic: topic) else { return respondError(payload: payload, reason: .noSessionForTopic, tag: tag) } - guard session.peerIsController, session.hasPermission(forEvent: event.name, onChain: params.chainId) else { + guard session.peerIsController, session.hasPermission(forEvent: event.name, onChain: payload.request.chainId) else { return respondError(payload: payload, reason: .unauthorizedEvent(event.name), tag: tag) } Task(priority: .high) { try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: tag) } - onEventReceived?(topic, event.publicRepresentation(), params.chainId) - } - - func setupExpirationSubscriptions() { - sessionStore.onSessionExpiration = { [weak self] session in - self?.kms.deletePrivateKey(for: session.selfParticipant.publicKey) - self?.kms.deleteAgreementSecret(for: session.topic) - } + onEventReceived?(topic, event.publicRepresentation(), payload.request.chainId) } } diff --git a/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift b/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift index b76b63d50..be4733984 100644 --- a/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift +++ b/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift @@ -1,7 +1,9 @@ import Foundation +import Combine +import JSONRPC import WalletConnectUtils import WalletConnectKMS -import Combine +import WalletConnectNetworking final class ControllerSessionStateMachine { @@ -22,9 +24,8 @@ final class ControllerSessionStateMachine { self.kms = kms self.sessionStore = sessionStore self.logger = logger - networkingInteractor.responsePublisher.sink { [unowned self] response in - handleResponse(response) - }.store(in: &publishers) + + setupSubscriptions() } func update(topic: String, namespaces: [String: SessionNamespace]) async throws { @@ -33,7 +34,8 @@ final class ControllerSessionStateMachine { try Namespace.validate(namespaces) logger.debug("Controller will update methods") sessionStore.setSession(session) - try await networkingInteractor.request(.wcSessionUpdate(SessionType.UpdateParams(namespaces: namespaces)), onTopic: topic) + let request = RPCRequest(method: SignProtocolMethod.sessionUpdate.method, params: SessionType.UpdateParams(namespaces: namespaces)) + try await networkingInteractor.request(request, topic: topic, tag: SignProtocolMethod.sessionUpdate.requestTag) } func extend(topic: String, by ttl: Int64) async throws { @@ -42,28 +44,32 @@ final class ControllerSessionStateMachine { try session.updateExpiry(by: ttl) let newExpiry = Int64(session.expiryDate.timeIntervalSince1970 ) sessionStore.setSession(session) - try await networkingInteractor.request(.wcSessionExtend(SessionType.UpdateExpiryParams(expiry: newExpiry)), onTopic: topic) + let request = RPCRequest(method: SignProtocolMethod.sessionExtend.method, params: SessionType.UpdateExpiryParams(expiry: newExpiry)) + try await networkingInteractor.request(request, topic: topic, tag: SignProtocolMethod.sessionExtend.requestTag) } // MARK: - Handle Response - private func handleResponse(_ response: WCResponse) { - switch response.requestParams { - case .sessionUpdate(let payload): - handleUpdateResponse(response: response, payload: payload) - case .sessionExtend(let payload): - handleUpdateExpiryResponse(response: response, payload: payload) - default: - break - } + private func setupSubscriptions() { + networkingInteractor.responseSubscription(on: SignProtocolMethod.sessionUpdate) + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + handleUpdateResponse(payload: payload) + } + .store(in: &publishers) + + networkingInteractor.responseSubscription(on: SignProtocolMethod.sessionExtend) + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + handleUpdateExpiryResponse(payload: payload) + } + .store(in: &publishers) } - private func handleUpdateResponse(response: WCResponse, payload: SessionType.UpdateParams) { - guard var session = sessionStore.getSession(forTopic: response.topic) else { return } - switch response.result { + private func handleUpdateResponse(payload: ResponseSubscriptionPayload) { + guard var session = sessionStore.getSession(forTopic: payload.topic) else { return } + switch payload.response { case .response: do { - try session.updateNamespaces(payload.namespaces, timestamp: response.timestamp) + try session.updateNamespaces(payload.request.namespaces, timestamp: payload.id.timestamp) if sessionStore.setSessionIfNewer(session) { onNamespacesUpdate?(session.topic, session.namespaces) @@ -76,12 +82,12 @@ final class ControllerSessionStateMachine { } } - private func handleUpdateExpiryResponse(response: WCResponse, payload: SessionType.UpdateExpiryParams) { - guard var session = sessionStore.getSession(forTopic: response.topic) else { return } - switch response.result { + private func handleUpdateExpiryResponse(payload: ResponseSubscriptionPayload) { + guard var session = sessionStore.getSession(forTopic: payload.topic) else { return } + switch payload.response { case .response: do { - try session.updateExpiry(to: payload.expiry) + try session.updateExpiry(to: payload.request.expiry) sessionStore.setSession(session) onExtend?(session.topic, session.expiryDate) } catch { diff --git a/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift b/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift index 0e4e22f66..46ab1c516 100644 --- a/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift +++ b/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift @@ -1,12 +1,10 @@ import Foundation import WalletConnectUtils import WalletConnectKMS +import WalletConnectNetworking import Combine final class NonControllerSessionStateMachine { - enum Errors: Error { - case respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) - } var onNamespacesUpdate: ((String, [String: SessionNamespace]) -> Void)? var onExtend: ((String, Date) -> Void)? @@ -25,32 +23,25 @@ final class NonControllerSessionStateMachine { self.kms = kms self.sessionStore = sessionStore self.logger = logger - setUpWCRequestHandling() + setupSubscriptions() } - private func setUpWCRequestHandling() { - networkingInteractor.wcRequestPublisher - .sink { [unowned self] subscriptionPayload in - do { - switch subscriptionPayload.wcRequest.params { - case .sessionUpdate(let updateParams): - try onSessionUpdateNamespacesRequest(payload: subscriptionPayload, updateParams: updateParams) - case .sessionExtend(let updateExpiryParams): - try onSessionUpdateExpiry(subscriptionPayload, updateExpiryParams: updateExpiryParams) - default: return - } - } catch Errors.respondError(let payload, let reason) { - respondError(payload: payload, reason: reason) - } catch { - logger.error("Unexpected Error: \(error.localizedDescription)") - } + private func setupSubscriptions() { + networkingInteractor.requestSubscription(on: SignProtocolMethod.sessionUpdate) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + onSessionUpdateNamespacesRequest(payload: payload, updateParams: payload.request) + }.store(in: &publishers) + + networkingInteractor.requestSubscription(on: SignProtocolMethod.sessionExtend) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + onSessionUpdateExpiry(payload: payload, updateExpiryParams: payload.request) }.store(in: &publishers) } - private func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) { + private func respondError(payload: SubscriptionPayload, reason: ReasonCode, tag: Int) { Task { do { - try await networkingInteractor.respondError(payload: payload, reason: reason) + try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: tag, reason: reason) } catch { logger.error("Respond Error failed with: \(error.localizedDescription)") } @@ -58,43 +49,51 @@ final class NonControllerSessionStateMachine { } // TODO: Update stored session namespaces - private func onSessionUpdateNamespacesRequest(payload: WCRequestSubscriptionPayload, updateParams: SessionType.UpdateParams) throws { + private func onSessionUpdateNamespacesRequest(payload: SubscriptionPayload, updateParams: SessionType.UpdateParams) { do { try Namespace.validate(updateParams.namespaces) } catch { - throw Errors.respondError(payload: payload, reason: .invalidUpdateRequest) + return respondError(payload: payload, reason: .invalidUpdateRequest, tag: SignProtocolMethod.sessionUpdate.responseTag) } guard var session = sessionStore.getSession(forTopic: payload.topic) else { - throw Errors.respondError(payload: payload, reason: .noSessionForTopic) + return respondError(payload: payload, reason: .noSessionForTopic, tag: SignProtocolMethod.sessionUpdate.responseTag) } guard session.peerIsController else { - throw Errors.respondError(payload: payload, reason: .unauthorizedUpdateRequest) + return respondError(payload: payload, reason: .unauthorizedUpdateRequest, tag: SignProtocolMethod.sessionUpdate.responseTag) } do { - try session.updateNamespaces(updateParams.namespaces, timestamp: payload.timestamp) + try session.updateNamespaces(updateParams.namespaces, timestamp: payload.id.timestamp) } catch { - throw Errors.respondError(payload: payload, reason: .invalidUpdateRequest) + return respondError(payload: payload, reason: .invalidUpdateRequest, tag: SignProtocolMethod.sessionUpdate.responseTag) } sessionStore.setSession(session) - networkingInteractor.respondSuccess(for: payload) + + Task(priority: .high) { + try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: SignProtocolMethod.sessionUpdate.responseTag) + } + onNamespacesUpdate?(session.topic, updateParams.namespaces) } - private func onSessionUpdateExpiry(_ payload: WCRequestSubscriptionPayload, updateExpiryParams: SessionType.UpdateExpiryParams) throws { + private func onSessionUpdateExpiry(payload: SubscriptionPayload, updateExpiryParams: SessionType.UpdateExpiryParams) { let topic = payload.topic guard var session = sessionStore.getSession(forTopic: topic) else { - throw Errors.respondError(payload: payload, reason: .noSessionForTopic) + return respondError(payload: payload, reason: .noSessionForTopic, tag: SignProtocolMethod.sessionExtend.responseTag) } guard session.peerIsController else { - throw Errors.respondError(payload: payload, reason: .unauthorizedExtendRequest) + return respondError(payload: payload, reason: .unauthorizedExtendRequest, tag: SignProtocolMethod.sessionExtend.responseTag) } do { try session.updateExpiry(to: updateExpiryParams.expiry) } catch { - throw Errors.respondError(payload: payload, reason: .invalidExtendRequest) + return respondError(payload: payload, reason: .invalidExtendRequest, tag: SignProtocolMethod.sessionExtend.responseTag) } sessionStore.setSession(session) - networkingInteractor.respondSuccess(for: payload) + + Task(priority: .high) { + try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: SignProtocolMethod.sessionExtend.responseTag) + } + onExtend?(session.topic, session.expiryDate) } } diff --git a/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcHistory.swift b/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcHistory.swift deleted file mode 100644 index 321bb5a62..000000000 --- a/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcHistory.swift +++ /dev/null @@ -1,63 +0,0 @@ -import Foundation -import WalletConnectUtils - -protocol JsonRpcHistoryRecording { - func get(id: Int64) -> JsonRpcRecord? - func set(topic: String, request: WCRequest, chainId: String?) throws - func delete(topic: String) - func resolve(response: JsonRpcResult) throws -> JsonRpcRecord - func exist(id: Int64) -> Bool -} -// TODO -remove and use jsonrpc history only from utils -class JsonRpcHistory: JsonRpcHistoryRecording { - let storage: CodableStore - let logger: ConsoleLogging - - init(logger: ConsoleLogging, keyValueStore: CodableStore) { - self.logger = logger - self.storage = keyValueStore - } - - func get(id: Int64) -> JsonRpcRecord? { - try? storage.get(key: "\(id)") - } - - func set(topic: String, request: WCRequest, chainId: String? = nil) throws { - guard !exist(id: request.id) else { - throw WalletConnectError.internal(.jsonRpcDuplicateDetected) - } - logger.debug("Setting JSON-RPC request history record - ID: \(request.id)") - let record = JsonRpcRecord(id: request.id, topic: topic, request: JsonRpcRecord.Request(method: request.method, params: request.params), response: nil, chainId: chainId) - storage.set(record, forKey: "\(request.id)") - } - - func delete(topic: String) { - storage.getAll().forEach { record in - if record.topic == topic { - storage.delete(forKey: "\(record.id)") - } - } - } - - func resolve(response: JsonRpcResult) throws -> JsonRpcRecord { - logger.debug("Resolving JSON-RPC response - ID: \(response.id)") - guard var record = try? storage.get(key: "\(response.id)") else { - throw WalletConnectError.internal(.noJsonRpcRequestMatchingResponse) - } - if record.response != nil { - throw WalletConnectError.internal(.jsonRpcDuplicateDetected) - } else { - record.response = response - storage.set(record, forKey: "\(record.id)") - return record - } - } - - func exist(id: Int64) -> Bool { - return (try? storage.get(key: "\(id)")) != nil - } - - public func getPending() -> [JsonRpcRecord] { - storage.getAll().filter {$0.response == nil} - } -} diff --git a/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcRecord.swift b/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcRecord.swift deleted file mode 100644 index edfb14dc8..000000000 --- a/Sources/WalletConnectSign/JsonRpcHistory/JsonRpcRecord.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation -import WalletConnectUtils - -struct JsonRpcRecord: Codable { - let id: Int64 - let topic: String - let request: Request - var response: JsonRpcResult? - let chainId: String? - - struct Request: Codable { - let method: WCRequest.Method - let params: WCRequest.Params - } -} diff --git a/Sources/WalletConnectSign/Response.swift b/Sources/WalletConnectSign/Response.swift index 8dd4a0f0f..c01e438e5 100644 --- a/Sources/WalletConnectSign/Response.swift +++ b/Sources/WalletConnectSign/Response.swift @@ -1,8 +1,9 @@ import Foundation +import JSONRPC import WalletConnectUtils public struct Response: Codable { public let topic: String public let chainId: String? - public let result: JsonRpcResult + public let result: RPCResult } diff --git a/Sources/WalletConnectSign/Sign/Sign.swift b/Sources/WalletConnectSign/Sign/Sign.swift index 42685d660..1005410cc 100644 --- a/Sources/WalletConnectSign/Sign/Sign.swift +++ b/Sources/WalletConnectSign/Sign/Sign.swift @@ -1,10 +1,14 @@ import Foundation +import Combine +import JSONRPC import WalletConnectUtils import WalletConnectRelay -import Combine +import WalletConnectNetworking public typealias Account = WalletConnectUtils.Account public typealias Blockchain = WalletConnectUtils.Blockchain +public typealias Reason = WalletConnectNetworking.Reason +public typealias RPCID = JSONRPC.RPCID /// Sign instatnce wrapper /// diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index befbca947..0d774c529 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -1,8 +1,10 @@ import Foundation +import Combine +import JSONRPC import WalletConnectRelay import WalletConnectUtils import WalletConnectKMS -import Combine +import WalletConnectNetworking /// WalletConnect Sign Client /// @@ -94,7 +96,7 @@ public final class SignClient { private let disconnectService: DisconnectService private let nonControllerSessionStateMachine: NonControllerSessionStateMachine private let controllerSessionStateMachine: ControllerSessionStateMachine - private let history: JsonRpcHistory + private let history: RPCHistory private let cleanupService: CleanupService private let sessionProposalPublisherSubject = PassthroughSubject() @@ -121,7 +123,7 @@ public final class SignClient { nonControllerSessionStateMachine: NonControllerSessionStateMachine, controllerSessionStateMachine: ControllerSessionStateMachine, disconnectService: DisconnectService, - history: JsonRpcHistory, + history: RPCHistory, cleanupService: CleanupService ) { self.logger = logger @@ -221,9 +223,10 @@ public final class SignClient { /// For the wallet to respond on pending dApp's JSON-RPC request /// - Parameters: /// - topic: Topic of the session for which the request was received. + /// - requestId: RPC request ID /// - response: Your JSON RPC response or an error. - public func respond(topic: String, response: JsonRpcResult) async throws { - try await sessionEngine.respondSessionRequest(topic: topic, response: response) + public func respond(topic: String, requestId: RPCID, response: RPCResult) async throws { + try await sessionEngine.respondSessionRequest(topic: topic, requestId: requestId, response: response) } /// Ping method allows to check if peer client is online and is subscribing for given topic @@ -289,10 +292,9 @@ public final class SignClient { /// - Parameter topic: topic representing session for which you want to get pending requests. If nil, you will receive pending requests for all active sessions. public func getPendingRequests(topic: String? = nil) -> [Request] { let pendingRequests: [Request] = history.getPending() - .filter {$0.request.method == .sessionRequest} .compactMap { - guard case let .sessionRequest(payloadRequest) = $0.request.params else {return nil} - return Request(id: $0.id, topic: $0.topic, method: payloadRequest.request.method, params: payloadRequest.request.params, chainId: payloadRequest.chainId) + guard let request = try? $0.request.params?.get(SessionType.RequestParams.self) else { return nil } + return Request(id: $0.id, topic: $0.topic, method: request.request.method, params: request.request.params, chainId: request.chainId) } if let topic = topic { return pendingRequests.filter {$0.topic == topic} @@ -303,11 +305,13 @@ public final class SignClient { /// - Parameter id: id of a wc_sessionRequest jsonrpc request /// - Returns: json rpc record object for given id or nil if record for give id does not exits - public func getSessionRequestRecord(id: Int64) -> WalletConnectUtils.JsonRpcRecord? { - guard let record = history.get(id: id), - case .sessionRequest(let payload) = record.request.params else {return nil} - let request = WalletConnectUtils.JsonRpcRecord.Request(method: payload.request.method, params: payload.request.params) - return WalletConnectUtils.JsonRpcRecord(id: record.id, topic: record.topic, request: request, response: record.response, chainId: record.chainId) + public func getSessionRequestRecord(id: Int64) -> Request? { + guard + let record = history.get(recordId: RPCID(id)), + let request = try? record.request.params?.get(SessionType.RequestParams.self) + else { return nil } + + return Request(id: record.id, topic: record.topic, method: record.request.method, params: request, chainId: request.chainId) } #if DEBUG @@ -326,7 +330,7 @@ public final class SignClient { sessionProposalPublisherSubject.send(proposal) } approveEngine.onSessionRejected = { [unowned self] proposal, reason in - sessionRejectionPublisherSubject.send((proposal, reason.publicRepresentation())) + sessionRejectionPublisherSubject.send((proposal, reason)) } approveEngine.onSessionSettle = { [unowned self] settledSession in sessionSettlePublisherSubject.send(settledSession) @@ -335,7 +339,7 @@ public final class SignClient { sessionRequestPublisherSubject.send(sessionRequest) } sessionEngine.onSessionDelete = { [unowned self] topic, reason in - sessionDeletePublisherSubject.send((topic, reason.publicRepresentation())) + sessionDeletePublisherSubject.send((topic, reason)) } controllerSessionStateMachine.onNamespacesUpdate = { [unowned self] topic, namespaces in sessionUpdatePublisherSubject.send((topic, namespaces)) diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 9a0412ad4..3ca457f06 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -3,6 +3,7 @@ import WalletConnectRelay import WalletConnectUtils import WalletConnectKMS import WalletConnectPairing +import WalletConnectNetworking public struct SignClientFactory { @@ -25,12 +26,12 @@ public struct SignClientFactory { static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> SignClient { let kms = KeyManagementService(keychain: keychainStorage) let serializer = Serializer(kms: kms) - let history = JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)) - let networkingInteractor = NetworkInteractor(relayClient: relayClient, serializer: serializer, logger: logger, jsonRpcHistory: history) + let rpcHistory = RPCHistory(keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)) + let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: rpcHistory) let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.pairings.rawValue))) let sessionStore = SessionStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.sessions.rawValue))) let sessionToPairingTopic = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.sessionToPairingTopic.rawValue) - let proposalPayloadsStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.proposals.rawValue) + let proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.proposals.rawValue) let pairingEngine = PairingEngine(networkingInteractor: networkingInteractor, kms: kms, pairingStore: pairingStore, metadata: metadata, logger: logger) let sessionEngine = SessionEngine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger) let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger) @@ -51,7 +52,7 @@ public struct SignClientFactory { approveEngine: approveEngine, nonControllerSessionStateMachine: nonControllerSessionStateMachine, controllerSessionStateMachine: controllerSessionStateMachine, disconnectService: disconnectService, - history: history, + history: rpcHistory, cleanupService: cleanupService ) return client diff --git a/Sources/WalletConnectSign/Types/Session/SessionType.swift b/Sources/WalletConnectSign/Types/Session/SessionType.swift index 054704eae..f450e94aa 100644 --- a/Sources/WalletConnectSign/Types/Session/SessionType.swift +++ b/Sources/WalletConnectSign/Types/Session/SessionType.swift @@ -1,5 +1,6 @@ import Foundation import WalletConnectUtils +import WalletConnectNetworking // Internal namespace for session payloads. internal enum SessionType { @@ -24,7 +25,7 @@ internal enum SessionType { typealias DeleteParams = SessionType.Reason - struct Reason: Codable, Equatable { + struct Reason: Codable, Equatable, WalletConnectNetworking.Reason { let code: Int let message: String @@ -64,15 +65,3 @@ internal enum SessionType { let expiry: Int64 } } - -internal extension Reason { - func internalRepresentation() -> SessionType.Reason { - SessionType.Reason(code: self.code, message: self.message) - } -} - -extension SessionType.Reason { - func publicRepresentation() -> Reason { - Reason(code: self.code, message: self.message) - } -} diff --git a/Sources/WalletConnectSign/Types/SignProtocolMethod.swift b/Sources/WalletConnectSign/Types/SignProtocolMethod.swift new file mode 100644 index 000000000..90d4a0c54 --- /dev/null +++ b/Sources/WalletConnectSign/Types/SignProtocolMethod.swift @@ -0,0 +1,72 @@ +import Foundation +import JSONRPC +import WalletConnectPairing +import WalletConnectUtils +import WalletConnectNetworking + +enum SignProtocolMethod: ProtocolMethod { + case pairingDelete + case pairingPing + case sessionPropose + case sessionSettle + case sessionUpdate + case sessionExtend + case sessionDelete + case sessionRequest + case sessionPing + case sessionEvent + + var method: String { + switch self { + case .pairingDelete: + return "wc_pairingDelete" + case .pairingPing: + return "wc_pairingPing" + case .sessionPropose: + return "wc_sessionPropose" + case .sessionSettle: + return "wc_sessionSettle" + case .sessionUpdate: + return "wc_sessionUpdate" + case .sessionExtend: + return "wc_sessionExtend" + case .sessionDelete: + return "wc_sessionDelete" + case .sessionRequest: + return "wc_sessionRequest" + case .sessionPing: + return "wc_sessionPing" + case .sessionEvent: + return "wc_sessionEvent" + } + } + + var requestTag: Int { + switch self { + case .pairingDelete: + return 1000 + case .pairingPing: + return 1002 + case .sessionPropose: + return 1100 + case .sessionSettle: + return 1102 + case .sessionUpdate: + return 1104 + case .sessionExtend: + return 1106 + case .sessionDelete: + return 1112 + case .sessionRequest: + return 1108 + case .sessionPing: + return 1114 + case .sessionEvent: + return 1110 + } + } + + var responseTag: Int { + return requestTag + 1 + } +} diff --git a/Sources/WalletConnectSign/Types/WCRequest.swift b/Sources/WalletConnectSign/Types/WCRequest.swift deleted file mode 100644 index a4a7e44df..000000000 --- a/Sources/WalletConnectSign/Types/WCRequest.swift +++ /dev/null @@ -1,85 +0,0 @@ -import Foundation -import JSONRPC -import WalletConnectPairing -import WalletConnectUtils -import WalletConnectNetworking - -enum WCRequest: Codable { - case pairingDelete(PairingType.DeleteParams) - case pairingPing(PairingType.PingParams) - case sessionPropose(SessionType.ProposeParams) - case sessionSettle(SessionType.SettleParams) - case sessionUpdate(SessionType.UpdateParams) - case sessionExtend(SessionType.UpdateExpiryParams) - case sessionDelete(SessionType.DeleteParams) - case sessionRequest(SessionType.RequestParams) - case sessionPing(SessionType.PingParams) - case sessionEvent(SessionType.EventParams) - - enum Method: ProtocolMethod { - case pairingDelete - case pairingPing - case sessionPropose - case sessionSettle - case sessionUpdate - case sessionExtend - case sessionDelete - case sessionRequest - case sessionPing - case sessionEvent - - var method: String { - switch self { - case .pairingDelete: - return "wc_pairingDelete" - case .pairingPing: - return "wc_pairingPing" - case .sessionPropose: - return "wc_sessionPropose" - case .sessionSettle: - return "wc_sessionSettle" - case .sessionUpdate: - return "wc_sessionUpdate" - case .sessionExtend: - return "wc_sessionExtend" - case .sessionDelete: - return "wc_sessionDelete" - case .sessionRequest: - return "wc_sessionRequest" - case .sessionPing: - return "wc_sessionPing" - case .sessionEvent: - return "wc_sessionEvent" - } - } - - var requestTag: Int { - switch self { - case .pairingDelete: - return 1000 - case .pairingPing: - return 1002 - case .sessionPropose: - return 1100 - case .sessionSettle: - return 1102 - case .sessionUpdate: - return 1104 - case .sessionExtend: - return 1106 - case .sessionDelete: - return 1112 - case .sessionRequest: - return 1108 - case .sessionPing: - return 1114 - case .sessionEvent: - return 1110 - } - } - - var responseTag: Int { - return requestTag + 1 - } - } -} diff --git a/Sources/WalletConnectUtils/JSONRPC/JSONRPCErrorResponse.swift b/Sources/WalletConnectUtils/JSONRPC/JSONRPCErrorResponse.swift deleted file mode 100644 index 15f2c3de1..000000000 --- a/Sources/WalletConnectUtils/JSONRPC/JSONRPCErrorResponse.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -public struct JSONRPCErrorResponse: Error, Equatable, Codable { - public let jsonrpc = "2.0" - public let id: Int64 - public let error: JSONRPCErrorResponse.Error - - enum CodingKeys: String, CodingKey { - case jsonrpc - case id - case error - } - - public init(id: Int64, error: JSONRPCErrorResponse.Error) { - self.id = id - self.error = error - } - - public struct Error: Codable, Equatable { - public let code: Int - public let message: String - public init(code: Int, message: String) { - self.code = code - self.message = message - } - } -} diff --git a/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift b/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift deleted file mode 100644 index 2c3f57bb9..000000000 --- a/Sources/WalletConnectUtils/JSONRPC/JSONRPCRequest.swift +++ /dev/null @@ -1,23 +0,0 @@ -import Foundation - -public struct JSONRPCRequest: Codable, Equatable { - - public let id: Int64 - public let jsonrpc: String - public let method: String - public let params: T - - public enum CodingKeys: CodingKey { - case id - case jsonrpc - case method - case params - } - - public init(id: Int64 = JsonRpcID.generate(), method: String, params: T) { - self.id = id - self.jsonrpc = "2.0" - self.method = method - self.params = params - } -} diff --git a/Sources/WalletConnectUtils/JSONRPC/JSONRPCResponse.swift b/Sources/WalletConnectUtils/JSONRPC/JSONRPCResponse.swift deleted file mode 100644 index e7dd48923..000000000 --- a/Sources/WalletConnectUtils/JSONRPC/JSONRPCResponse.swift +++ /dev/null @@ -1,18 +0,0 @@ -import Foundation - -public struct JSONRPCResponse: Codable, Equatable { - public let jsonrpc = "2.0" - public let id: Int64 - public let result: T - - enum CodingKeys: String, CodingKey { - case jsonrpc - case id - case result - } - - public init(id: Int64, result: T) { - self.id = id - self.result = result - } -} diff --git a/Sources/WalletConnectUtils/JSONRPC/JsonRpcResult.swift b/Sources/WalletConnectUtils/JSONRPC/JsonRpcResult.swift deleted file mode 100644 index 611712500..000000000 --- a/Sources/WalletConnectUtils/JSONRPC/JsonRpcResult.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Foundation - -public enum JsonRpcResult: Codable { - case error(JSONRPCErrorResponse) - case response(JSONRPCResponse) - - public var id: Int64 { - switch self { - case .error(let value): - return value.id - case .response(let value): - return value.id - } - } - - public var value: Codable { - switch self { - case .error(let value): - return value - case .response(let value): - return value - } - } -} diff --git a/Sources/WalletConnectUtils/JsonRpcHistory.swift b/Sources/WalletConnectUtils/JsonRpcHistory.swift deleted file mode 100644 index 9616b794b..000000000 --- a/Sources/WalletConnectUtils/JsonRpcHistory.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation - -public class JsonRpcHistory where T: Codable&Equatable { - enum RecordingError: Error { - case jsonRpcDuplicateDetected - case noJsonRpcRequestMatchingResponse - } - private let storage: CodableStore - private let logger: ConsoleLogging - - public init(logger: ConsoleLogging, keyValueStore: CodableStore) { - self.logger = logger - self.storage = keyValueStore - } - - public func get(id: Int64) -> JsonRpcRecord? { - try? storage.get(key: "\(id)") - } - - public func set(topic: String, request: JSONRPCRequest, chainId: String? = nil) throws { - guard !exist(id: request.id) else { - throw RecordingError.jsonRpcDuplicateDetected - } - logger.debug("Setting JSON-RPC request history record") - let record = JsonRpcRecord(id: request.id, topic: topic, request: JsonRpcRecord.Request(method: request.method, params: AnyCodable(request.params)), response: nil, chainId: chainId) - storage.set(record, forKey: "\(request.id)") - } - - public func delete(topic: String) { - storage.getAll().forEach { record in - if record.topic == topic { - storage.delete(forKey: "\(record.id)") - } - } - } - - public func resolve(response: JsonRpcResult) throws -> JsonRpcRecord { - logger.debug("Resolving JSON-RPC response - ID: \(response.id)") - guard var record = try? storage.get(key: "\(response.id)") else { - throw RecordingError.noJsonRpcRequestMatchingResponse - } - if record.response != nil { - throw RecordingError.jsonRpcDuplicateDetected - } else { - record.response = response - storage.set(record, forKey: "\(record.id)") - return record - } - } - - public func exist(id: Int64) -> Bool { - return (try? storage.get(key: "\(id)")) != nil - } - - public func getPending() -> [JsonRpcRecord] { - storage.getAll().filter {$0.response == nil} - } -} diff --git a/Sources/WalletConnectUtils/JsonRpcRecord.swift b/Sources/WalletConnectUtils/JsonRpcRecord.swift deleted file mode 100644 index 48013221f..000000000 --- a/Sources/WalletConnectUtils/JsonRpcRecord.swift +++ /dev/null @@ -1,27 +0,0 @@ -import Foundation - -public struct JsonRpcRecord: Codable { - public let id: Int64 - public let topic: String - public let request: Request - public var response: JsonRpcResult? - public let chainId: String? - - public init(id: Int64, topic: String, request: JsonRpcRecord.Request, response: JsonRpcResult? = nil, chainId: String?) { - self.id = id - self.topic = topic - self.request = request - self.response = response - self.chainId = chainId - } - - public struct Request: Codable { - public let method: String - public let params: AnyCodable - - public init(method: String, params: AnyCodable) { - self.method = method - self.params = params - } - } -} diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 0ddc96adc..7942fdac1 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -26,10 +26,9 @@ public class NetworkingInteractorMock: NetworkInteracting { } // TODO: Avoid copy paste from NetworkInteractor - public func requestSubscription(on request: ProtocolMethod?) -> AnyPublisher, Never> { + public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return requestPublisher .filter { rpcRequest in - guard let request = request else { return true } return rpcRequest.request.method == request.method } .compactMap { topic, rpcRequest in @@ -40,10 +39,9 @@ public class NetworkingInteractorMock: NetworkInteracting { } // TODO: Avoid copy paste from NetworkInteractor - public func responseSubscription(on request: ProtocolMethod?) -> AnyPublisher, Never> { + public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { rpcRequest in - guard let request = request else { return true } return rpcRequest.request.method == request.method } .compactMap { topic, rpcRequest, rpcResponse in @@ -56,12 +54,13 @@ public class NetworkingInteractorMock: NetworkInteracting { .eraseToAnyPublisher() } - public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher { + // TODO: Avoid copy paste from NetworkInteractor + public func responseErrorSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { $0.request.method == request.method } - .compactMap { (_, _, rpcResponse) in - guard let id = rpcResponse.id, let error = rpcResponse.error else { return nil } - return ResponseSubscriptionErrorPayload(id: id, error: error) + .compactMap { (topic, rpcRequest, rpcResponse) in + guard let id = rpcResponse.id, let request = try? rpcRequest.params?.get(Request.self), let error = rpcResponse.error else { return nil } + return ResponseSubscriptionErrorPayload(id: id, topic: topic, request: request, error: error) } .eraseToAnyPublisher() } From 41a54d556f6fded8082ad9a92c296fcf4cef9593 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 8 Sep 2022 22:51:54 +0300 Subject: [PATCH 07/40] Sign new ping --- .../Sign/Helpers/ClientDelegate.swift | 10 +++++ .../Sign/SignClientTests.swift | 41 +++++++++++++++++-- .../Services/PairingPingService.swift | 12 +++--- .../Services/PingRequester.swift | 17 ++++---- .../Services/PingResponder.swift | 11 +++-- .../Services/PingResponseSubscriber.swift | 11 +++-- .../Engine/Common/PairingEngine.swift | 17 -------- .../Engine/Common/SessionEngine.swift | 17 -------- .../Services/SessionPingService.swift | 35 ++++++++++++++++ .../WalletConnectSign/Sign/SignClient.swift | 39 ++++++++++++++---- .../Sign/SignClientFactory.swift | 4 ++ 11 files changed, 147 insertions(+), 67 deletions(-) create mode 100644 Sources/WalletConnectSign/Services/SessionPingService.swift diff --git a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift index 71ac99ab3..ab7b43e11 100644 --- a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift +++ b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift @@ -14,6 +14,8 @@ class ClientDelegate { var onSessionDelete: (() -> Void)? var onSessionUpdateNamespaces: ((String, [String: SessionNamespace]) -> Void)? var onSessionExtend: ((String, Date) -> Void)? + var onSessionPing: ((String) -> Void)? + var onPairingPing: ((String) -> Void)? var onEventReceived: ((Session.Event, String) -> Void)? private var publishers = Set() @@ -63,5 +65,13 @@ class ClientDelegate { client.sessionExtendPublisher.sink { (topic, date) in self.onSessionExtend?(topic, date) }.store(in: &publishers) + + client.sessionPingResponsePublisher.sink { topic in + self.onSessionPing?(topic) + }.store(in: &publishers) + + client.pairingPingResponsePublisher.sink { topic in + self.onPairingPing?(topic) + }.store(in: &publishers) } } diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 3dd004d78..8a30c72cc 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -137,20 +137,54 @@ final class SignClientTests: XCTestCase { wait(for: [sessionDeleteExpectation], timeout: defaultTimeout) } - func testNewPairingPing() async throws { + func testPairingPing() async throws { let pongResponseExpectation = expectation(description: "Ping sender receives a pong response") let uri = try await dapp.client.connect(requiredNamespaces: ProposalNamespace.stubRequired())! try await wallet.client.pair(uri: uri) let pairing = wallet.client.getPairings().first! - wallet.client.ping(topic: pairing.topic) { result in - if case .failure = result { XCTFail() } + + wallet.onPairingPing = { topic in + XCTAssertEqual(topic, pairing.topic) pongResponseExpectation.fulfill() } + + try await wallet.client.ping(topic: pairing.topic) + wait(for: [pongResponseExpectation], timeout: defaultTimeout) } + func testSessionPing() async throws { + let expectation = expectation(description: "Proposer receives ping response") + + let requiredNamespaces = ProposalNamespace.stubRequired() + let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) + + wallet.onSessionProposal = { proposal in + Task(priority: .high) { + try! await self.wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + } + } + + dapp.onSessionSettled = { sessionSettled in + Task(priority: .high) { + try! await self.dapp.client.ping(topic: sessionSettled.topic) + } + } + + dapp.onSessionPing = { topic in + let session = self.wallet.client.getSessions().first! + XCTAssertEqual(topic, session.topic) + expectation.fulfill() + } + + let uri = try await dapp.client.connect(requiredNamespaces: requiredNamespaces)! + try await wallet.client.pair(uri: uri) + + wait(for: [expectation], timeout: .infinity) + } + func testSessionRequest() async throws { let requestExpectation = expectation(description: "Wallet expects to receive a request") let responseExpectation = expectation(description: "Dapp expects to receive a response") @@ -242,7 +276,6 @@ final class SignClientTests: XCTestCase { wait(for: [expectation], timeout: defaultTimeout) } - func testNewSessionOnExistingPairing() async { let dappSettlementExpectation = expectation(description: "Dapp settles session") dappSettlementExpectation.expectedFulfillmentCount = 2 diff --git a/Sources/WalletConnectPairing/Services/PairingPingService.swift b/Sources/WalletConnectPairing/Services/PairingPingService.swift index 6baef98e0..f435ec72b 100644 --- a/Sources/WalletConnectPairing/Services/PairingPingService.swift +++ b/Sources/WalletConnectPairing/Services/PairingPingService.swift @@ -1,8 +1,9 @@ -import WalletConnectUtils import Foundation +import WalletConnectUtils import WalletConnectNetworking public class PairingPingService { + private let pairingStorage: WCPairingStorage private let pingRequester: PingRequester private let pingResponder: PingResponder private let pingResponseSubscriber: PingResponseSubscriber @@ -20,13 +21,14 @@ public class PairingPingService { pairingStorage: WCPairingStorage, networkingInteractor: NetworkInteracting, logger: ConsoleLogging) { - pingRequester = PingRequester(pairingStorage: pairingStorage, networkingInteractor: networkingInteractor) - pingResponder = PingResponder(networkingInteractor: networkingInteractor, logger: logger) - pingResponseSubscriber = PingResponseSubscriber(networkingInteractor: networkingInteractor, logger: logger) + self.pairingStorage = pairingStorage + self.pingRequester = PingRequester(networkingInteractor: networkingInteractor, method: PairingProtocolMethod.ping) + self.pingResponder = PingResponder(networkingInteractor: networkingInteractor, method: PairingProtocolMethod.ping, logger: logger) + self.pingResponseSubscriber = PingResponseSubscriber(networkingInteractor: networkingInteractor, method: PairingProtocolMethod.ping, logger: logger) } public func ping(topic: String) async throws { + guard pairingStorage.hasPairing(forTopic: topic) else { return } try await pingRequester.ping(topic: topic) } - } diff --git a/Sources/WalletConnectPairing/Services/PingRequester.swift b/Sources/WalletConnectPairing/Services/PingRequester.swift index f936a8c9c..9def19d28 100644 --- a/Sources/WalletConnectPairing/Services/PingRequester.swift +++ b/Sources/WalletConnectPairing/Services/PingRequester.swift @@ -1,19 +1,18 @@ import Foundation -import WalletConnectNetworking import JSONRPC +import WalletConnectNetworking -class PingRequester { - private let pairingStorage: WCPairingStorage +public class PingRequester { + private let method: ProtocolMethod private let networkingInteractor: NetworkInteracting - init(pairingStorage: WCPairingStorage, networkingInteractor: NetworkInteracting) { - self.pairingStorage = pairingStorage + public init(networkingInteractor: NetworkInteracting, method: ProtocolMethod) { + self.method = method self.networkingInteractor = networkingInteractor } - func ping(topic: String) async throws { - guard pairingStorage.hasPairing(forTopic: topic) else { return } - let request = RPCRequest(method: PairingProtocolMethod.ping.rawValue, params: PairingPingParams()) - try await networkingInteractor.request(request, topic: topic, tag: PairingProtocolMethod.ping.requestTag) + public func ping(topic: String) async throws { + let request = RPCRequest(method: method.method, params: PairingPingParams()) + try await networkingInteractor.request(request, topic: topic, tag: method.requestTag) } } diff --git a/Sources/WalletConnectPairing/Services/PingResponder.swift b/Sources/WalletConnectPairing/Services/PingResponder.swift index 9ed8c0806..7718f6128 100644 --- a/Sources/WalletConnectPairing/Services/PingResponder.swift +++ b/Sources/WalletConnectPairing/Services/PingResponder.swift @@ -2,24 +2,27 @@ import Combine import WalletConnectUtils import WalletConnectNetworking -class PingResponder { +public class PingResponder { private let networkingInteractor: NetworkInteracting + private let method: ProtocolMethod private let logger: ConsoleLogging private var publishers = [AnyCancellable]() - init(networkingInteractor: NetworkInteracting, + public init(networkingInteractor: NetworkInteracting, + method: ProtocolMethod, logger: ConsoleLogging) { self.networkingInteractor = networkingInteractor + self.method = method self.logger = logger subscribePingRequests() } private func subscribePingRequests() { - networkingInteractor.requestSubscription(on: PairingProtocolMethod.ping) + networkingInteractor.requestSubscription(on: method) .sink { [unowned self] (payload: RequestSubscriptionPayload) in logger.debug("Responding for pairing ping") Task(priority: .high) { - try? await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: PairingProtocolMethod.ping.responseTag) + try? await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, tag: method.responseTag) } } .store(in: &publishers) diff --git a/Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift b/Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift index d2ce74a89..e473bd3e4 100644 --- a/Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift +++ b/Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift @@ -2,22 +2,25 @@ import Combine import WalletConnectUtils import WalletConnectNetworking -class PingResponseSubscriber { +public class PingResponseSubscriber { private let networkingInteractor: NetworkInteracting + private let method: ProtocolMethod private let logger: ConsoleLogging private var publishers = [AnyCancellable]() - var onResponse: ((String)->())? + public var onResponse: ((String)->())? - init(networkingInteractor: NetworkInteracting, + public init(networkingInteractor: NetworkInteracting, + method: ProtocolMethod, logger: ConsoleLogging) { self.networkingInteractor = networkingInteractor + self.method = method self.logger = logger subscribePingResponses() } private func subscribePingResponses() { - networkingInteractor.responseSubscription(on: PairingProtocolMethod.ping) + networkingInteractor.responseSubscription(on: method) .sink { [unowned self] (payload: ResponseSubscriptionPayload) in onResponse?(payload.topic) } diff --git a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift index 0561e6446..7c75d9876 100644 --- a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift @@ -75,23 +75,6 @@ final class PairingEngine { let request = RPCRequest(method: SignProtocolMethod.sessionPropose.method, params: proposal) try await networkingInteractor.request(request, topic: pairingTopic, tag: SignProtocolMethod.sessionPropose.requestTag) } - - func ping(topic: String, completion: @escaping ((Result) -> Void)) { - guard pairingStore.hasPairing(forTopic: topic) else { - logger.debug("Could not find pairing to ping for topic \(topic)") - return - } -// TODO: Ping disabled -// networkingInteractor.requestPeerResponse(.wcPairingPing, onTopic: topic) { [unowned self] result in -// switch result { -// case .success: -// logger.debug("Did receive ping response") -// completion(.success(())) -// case .failure(let error): -// logger.debug("error: \(error)") -// } -// } - } } // MARK: Private diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 0905d4f3d..60435dc0e 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -47,23 +47,6 @@ final class SessionEngine { sessionStore.getAll().map {$0.publicRepresentation()} } - func ping(topic: String, completion: @escaping (Result) -> Void) { - guard sessionStore.hasSession(forTopic: topic) else { - logger.debug("Could not find session to ping for topic \(topic)") - return - } -// TODO: Ping disabled -// networkingInteractor.requestPeerResponse(.wcSessionPing, onTopic: topic) { [unowned self] result in -// switch result { -// case .success: -// logger.debug("Did receive ping response") -// completion(.success(())) -// case .failure(let error): -// logger.debug("error: \(error)") -// } -// } - } - func request(_ request: Request) async throws { logger.debug("will request on session topic: \(request.topic)") guard let session = sessionStore.getSession(forTopic: request.topic), session.acknowledged else { diff --git a/Sources/WalletConnectSign/Services/SessionPingService.swift b/Sources/WalletConnectSign/Services/SessionPingService.swift new file mode 100644 index 000000000..697dcab89 --- /dev/null +++ b/Sources/WalletConnectSign/Services/SessionPingService.swift @@ -0,0 +1,35 @@ +import Foundation +import WalletConnectPairing +import WalletConnectUtils +import WalletConnectNetworking + +class SessionPingService { + private let sessionStorage: WCSessionStorage + private let pingRequester: PingRequester + private let pingResponder: PingResponder + private let pingResponseSubscriber: PingResponseSubscriber + + var onResponse: ((String)->())? { + get { + return pingResponseSubscriber.onResponse + } + set { + pingResponseSubscriber.onResponse = newValue + } + } + + init( + sessionStorage: WCSessionStorage, + networkingInteractor: NetworkInteracting, + logger: ConsoleLogging) { + self.sessionStorage = sessionStorage + self.pingRequester = PingRequester(networkingInteractor: networkingInteractor, method: SignProtocolMethod.sessionPing) + self.pingResponder = PingResponder(networkingInteractor: networkingInteractor, method: SignProtocolMethod.sessionPing, logger: logger) + self.pingResponseSubscriber = PingResponseSubscriber(networkingInteractor: networkingInteractor, method: SignProtocolMethod.sessionPing, logger: logger) + } + + func ping(topic: String) async throws { + guard sessionStorage.hasSession(forTopic: topic) else { return } + try await pingRequester.ping(topic: topic) + } +} diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 0d774c529..4d343153a 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -5,6 +5,7 @@ import WalletConnectRelay import WalletConnectUtils import WalletConnectKMS import WalletConnectNetworking +import WalletConnectPairing /// WalletConnect Sign Client /// @@ -83,6 +84,20 @@ public final class SignClient { sessionExtendPublisherSubject.eraseToAnyPublisher() } + /// Publisher that sends session topic when session ping received + /// + /// Event will be emited on controller and non-controller clients. + public var sessionPingResponsePublisher: AnyPublisher { + sessionPingResponsePublisherSubject.eraseToAnyPublisher() + } + + /// Publisher that sends pairing topic when pairing ping received + /// + /// Event will be emited on controller and non-controller clients. + public var pairingPingResponsePublisher: AnyPublisher { + pairingPingResponsePublisherSubject.eraseToAnyPublisher() + } + /// An object that loggs SDK's errors and info messages public let logger: ConsoleLogging @@ -94,6 +109,8 @@ public final class SignClient { private let sessionEngine: SessionEngine private let approveEngine: ApproveEngine private let disconnectService: DisconnectService + private let pairingPingService: PairingPingService + private let sessionPingService: SessionPingService private let nonControllerSessionStateMachine: NonControllerSessionStateMachine private let controllerSessionStateMachine: ControllerSessionStateMachine private let history: RPCHistory @@ -109,6 +126,8 @@ public final class SignClient { private let sessionUpdatePublisherSubject = PassthroughSubject<(sessionTopic: String, namespaces: [String: SessionNamespace]), Never>() private let sessionEventPublisherSubject = PassthroughSubject<(event: Session.Event, sessionTopic: String, chainId: Blockchain?), Never>() private let sessionExtendPublisherSubject = PassthroughSubject<(sessionTopic: String, date: Date), Never>() + private let sessionPingResponsePublisherSubject = PassthroughSubject() + private let pairingPingResponsePublisherSubject = PassthroughSubject() private var publishers = Set() @@ -120,6 +139,8 @@ public final class SignClient { pairEngine: PairEngine, sessionEngine: SessionEngine, approveEngine: ApproveEngine, + pairingPingService: PairingPingService, + sessionPingService: SessionPingService, nonControllerSessionStateMachine: NonControllerSessionStateMachine, controllerSessionStateMachine: ControllerSessionStateMachine, disconnectService: DisconnectService, @@ -132,6 +153,8 @@ public final class SignClient { self.pairEngine = pairEngine self.sessionEngine = sessionEngine self.approveEngine = approveEngine + self.pairingPingService = pairingPingService + self.sessionPingService = sessionPingService self.nonControllerSessionStateMachine = nonControllerSessionStateMachine self.controllerSessionStateMachine = controllerSessionStateMachine self.history = history @@ -238,15 +261,11 @@ public final class SignClient { /// - Parameters: /// - topic: Topic of a session or a pairing /// - completion: Result will be success on response or an error - public func ping(topic: String, completion: @escaping ((Result) -> Void)) { + public func ping(topic: String) async throws { if pairingEngine.hasPairing(for: topic) { - pairingEngine.ping(topic: topic) { result in - completion(result) - } + try await pairingPingService.ping(topic: topic) } else if sessionEngine.hasSession(for: topic) { - sessionEngine.ping(topic: topic) { result in - completion(result) - } + try await sessionPingService.ping(topic: topic) } } @@ -359,6 +378,12 @@ public final class SignClient { sessionEngine.onSessionResponse = { [unowned self] response in sessionResponsePublisherSubject.send(response) } + pairingPingService.onResponse = { [unowned self] topic in + pairingPingResponsePublisherSubject.send(topic) + } + sessionPingService.onResponse = { [unowned self] topic in + sessionPingResponsePublisherSubject.send(topic) + } } private func setUpConnectionObserving() { diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 3ca457f06..694b68c48 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -42,6 +42,8 @@ public struct SignClientFactory { let deletePairingService = DeletePairingService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore, logger: logger) let deleteSessionService = DeleteSessionService(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger) let disconnectService = DisconnectService(deletePairingService: deletePairingService, deleteSessionService: deleteSessionService, pairingStorage: pairingStore, sessionStorage: sessionStore) + let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingInteractor, logger: logger) + let pairingPingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingInteractor, logger: logger) let client = SignClient( logger: logger, @@ -50,6 +52,8 @@ public struct SignClientFactory { pairEngine: pairEngine, sessionEngine: sessionEngine, approveEngine: approveEngine, + pairingPingService: pairingPingService, + sessionPingService: sessionPingService, nonControllerSessionStateMachine: nonControllerSessionStateMachine, controllerSessionStateMachine: controllerSessionStateMachine, disconnectService: disconnectService, history: rpcHistory, From 3a734eac9f22585a1c611cd8edba9821d33c5a7a Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 8 Sep 2022 22:53:25 +0300 Subject: [PATCH 08/40] Cleanup --- .../NetworkInteractor/NetworkInteractor.swift | 259 ------------------ .../NetworkInteractor/NetworkRelaying.swift | 19 -- 2 files changed, 278 deletions(-) delete mode 100644 Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift delete mode 100644 Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift deleted file mode 100644 index f23cc922f..000000000 --- a/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift +++ /dev/null @@ -1,259 +0,0 @@ -//import Foundation -//import Combine -//import WalletConnectUtils -//import WalletConnectKMS -// -//protocol NetworkInteracting: AnyObject { -// var transportConnectionPublisher: AnyPublisher {get} -// var wcRequestPublisher: AnyPublisher {get} -// var responsePublisher: AnyPublisher {get} -// /// Completes when request sent from a networking client -// func request(_ wcMethod: WCMethod, onTopic topic: String) async throws -// /// Completes with an acknowledgement from the relay network -// func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void)) -// /// Completes with a peer response -// func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) -// func respond(topic: String, response: JsonRpcResult, tag: Int) async throws -// func respondSuccess(payload: WCRequestSubscriptionPayload) async throws -// func respondSuccess(for payload: WCRequestSubscriptionPayload) -// func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws -// func subscribe(topic: String) async throws -// func unsubscribe(topic: String) -//} -// -//extension NetworkInteracting { -// func request(_ wcMethod: WCMethod, onTopic topic: String) { -// requestPeerResponse(wcMethod, onTopic: topic, completion: nil) -// } -//} -// -//class NetworkInteractor: NetworkInteracting { -// -// private var publishers = Set() -// -// private var relayClient: NetworkRelaying -// private let serializer: Serializing -// private let jsonRpcHistory: JsonRpcHistoryRecording -// -// private let transportConnectionPublisherSubject = PassthroughSubject() -// private let responsePublisherSubject = PassthroughSubject() -// private let wcRequestPublisherSubject = PassthroughSubject() -// -// var transportConnectionPublisher: AnyPublisher { -// transportConnectionPublisherSubject.eraseToAnyPublisher() -// } -// var wcRequestPublisher: AnyPublisher { -// wcRequestPublisherSubject.eraseToAnyPublisher() -// } -// var responsePublisher: AnyPublisher { -// responsePublisherSubject.eraseToAnyPublisher() -// } -// -// let logger: ConsoleLogging -// -// init(relayClient: NetworkRelaying, -// serializer: Serializing, -// logger: ConsoleLogging, -// jsonRpcHistory: JsonRpcHistoryRecording) { -// self.relayClient = relayClient -// self.serializer = serializer -// self.logger = logger -// self.jsonRpcHistory = jsonRpcHistory -// setUpPublishers() -// } -// -// func request(_ wcMethod: WCMethod, onTopic topic: String) async throws { -// try await request(topic: topic, payload: wcMethod.asRequest()) -// } -// -// /// Completes when networking client sends a request -// func request(topic: String, payload: WCRequest) async throws { -// try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) -// let message = try serializer.serialize(topic: topic, encodable: payload) -// let prompt = shouldPrompt(payload.method) -// try await relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) -// } -// -// func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) { -// let payload = wcMethod.asRequest() -// do { -// try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) -// let message = try serializer.serialize(topic: topic, encodable: payload) -// let prompt = shouldPrompt(payload.method) -// relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) { [weak self] error in -// guard let self = self else {return} -// if let error = error { -// self.logger.error(error) -// } else { -// var cancellable: AnyCancellable! -// cancellable = self.responsePublisher -// .filter {$0.result.id == payload.id} -// .sink { (response) in -// cancellable.cancel() -// self.logger.debug("WC Relay - received response on topic: \(topic)") -// switch response.result { -// case .response(let response): -// completion?(.success(response)) -// case .error(let error): -// self.logger.debug("Request error: \(error)") -// completion?(.failure(error)) -// } -// } -// } -// } -// } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { -// logger.info("Info: Json Rpc Duplicate Detected") -// } catch { -// logger.error(error) -// } -// } -// -// /// Completes with an acknowledgement from the relay network. -// /// completes with error if networking client was not able to send a message -// func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void)) { -// do { -// let payload = wcMethod.asRequest() -// try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) -// let message = try serializer.serialize(topic: topic, encodable: payload) -// let prompt = shouldPrompt(payload.method) -// relayClient.publish(topic: topic, payload: message, tag: payload.tag, prompt: prompt) { error in -// completion(error) -// } -// } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { -// logger.info("Info: Json Rpc Duplicate Detected") -// } catch { -// logger.error(error) -// } -// } -// -// func respond(topic: String, response: JsonRpcResult, tag: Int) async throws { -// _ = try jsonRpcHistory.resolve(response: response) -// -// let message = try serializer.serialize(topic: topic, encodable: response.value) -// logger.debug("Responding....topic: \(topic)") -// -// do { -// try await relayClient.publish(topic: topic, payload: message, tag: tag, prompt: false) -// } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { -// logger.info("Info: Json Rpc Duplicate Detected") -// } -// } -// -// func respondSuccess(payload: WCRequestSubscriptionPayload) async throws { -// let response = JSONRPCResponse(id: payload.wcRequest.id, result: AnyCodable(true)) -// try await respond(topic: payload.topic, response: JsonRpcResult.response(response), tag: payload.wcRequest.responseTag) -// } -// -// func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws { -// let response = JSONRPCErrorResponse(id: payload.wcRequest.id, error: JSONRPCErrorResponse.Error(code: reason.code, message: reason.message)) -// try await respond(topic: payload.topic, response: JsonRpcResult.error(response), tag: payload.wcRequest.responseTag) -// } -// -// // TODO: Move to async -// func respondSuccess(for payload: WCRequestSubscriptionPayload) { -// Task(priority: .background) { -// do { -// try await respondSuccess(payload: payload) -// } catch { -// self.logger.error("Respond Success failed with: \(error.localizedDescription)") -// } -// } -// } -// -// func subscribe(topic: String) async throws { -// try await relayClient.subscribe(topic: topic) -// } -// -// func unsubscribe(topic: String) { -// relayClient.unsubscribe(topic: topic) { [weak self] error in -// if let error = error { -// self?.logger.error(error) -// } else { -// self?.jsonRpcHistory.delete(topic: topic) -// } -// } -// } -// -// // MARK: - Private -// -// private func setUpPublishers() { -// relayClient.socketConnectionStatusPublisher.sink { [weak self] status in -// if status == .connected { -// self?.transportConnectionPublisherSubject.send() -// } -// }.store(in: &publishers) -// -// relayClient.messagePublisher.sink { [weak self] (topic, message) in -// self?.manageSubscription(topic, message) -// } -// .store(in: &publishers) -// } -// -// private func manageSubscription(_ topic: String, _ encodedEnvelope: String) { -// if let deserializedJsonRpcRequest: WCRequest = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { -// handleWCRequest(topic: topic, request: deserializedJsonRpcRequest) -// } else if let deserializedJsonRpcResponse: JSONRPCResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { -// handleJsonRpcResponse(response: deserializedJsonRpcResponse) -// } else if let deserializedJsonRpcError: JSONRPCErrorResponse = serializer.tryDeserialize(topic: topic, encodedEnvelope: encodedEnvelope) { -// handleJsonRpcErrorResponse(response: deserializedJsonRpcError) -// } else { -// logger.warn("Warning: Networking Interactor - Received unknown object type from networking relay") -// } -// } -// -// private func handleWCRequest(topic: String, request: WCRequest) { -// do { -// try jsonRpcHistory.set(topic: topic, request: request, chainId: getChainId(request)) -// let payload = WCRequestSubscriptionPayload(topic: topic, wcRequest: request) -// wcRequestPublisherSubject.send(payload) -// } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { -// logger.info("Info: Json Rpc Duplicate Detected") -// } catch { -// logger.error(error) -// } -// } -// -// private func handleJsonRpcResponse(response: JSONRPCResponse) { -// do { -// let record = try jsonRpcHistory.resolve(response: JsonRpcResult.response(response)) -// let wcResponse = WCResponse( -// topic: record.topic, -// chainId: record.chainId, -// requestMethod: record.request.method, -// requestParams: record.request.params, -// result: JsonRpcResult.response(response)) -// responsePublisherSubject.send(wcResponse) -// } catch { -// logger.info("Info: \(error.localizedDescription)") -// } -// } -// -// private func handleJsonRpcErrorResponse(response: JSONRPCErrorResponse) { -// do { -// let record = try jsonRpcHistory.resolve(response: JsonRpcResult.error(response)) -// let wcResponse = WCResponse( -// topic: record.topic, -// chainId: record.chainId, -// requestMethod: record.request.method, -// requestParams: record.request.params, -// result: JsonRpcResult.error(response)) -// responsePublisherSubject.send(wcResponse) -// } catch { -// logger.info("Info: \(error.localizedDescription)") -// } -// } -// -// private func shouldPrompt(_ method: WCRequest.Method) -> Bool { -// switch method { -// case .sessionRequest: -// return true -// default: -// return false -// } -// } -// -// func getChainId(_ request: WCRequest) -> String? { -// guard case let .sessionRequest(payload) = request.params else {return nil} -// return payload.chainId.absoluteString -// } -//} diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift deleted file mode 100644 index fec9c6ed8..000000000 --- a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift +++ /dev/null @@ -1,19 +0,0 @@ -//import Foundation -//import WalletConnectRelay -//import Combine -// -//extension RelayClient: NetworkRelaying {} -// -//protocol NetworkRelaying { -// var messagePublisher: AnyPublisher<(topic: String, message: String), Never> { get } -// var socketConnectionStatusPublisher: AnyPublisher { get } -// func connect() throws -// func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws -// func publish(topic: String, payload: String, tag: Int, prompt: Bool) async throws -// /// - returns: request id -// func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) -// func subscribe(topic: String, completion: @escaping (Error?) -> Void) -// func subscribe(topic: String) async throws -// /// - returns: request id -// func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) -//} From e19148db4a98908e7bf1bb2537fa9bd59d94d75f Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 9 Sep 2022 02:54:08 +0300 Subject: [PATCH 09/40] Unit tests fixed --- Package.swift | 2 +- .../Engine/Common/ApproveEngine.swift | 2 +- .../Engine/Common/SessionEngine.swift | 4 +- .../NonControllerSessionStateMachine.swift | 2 +- .../NetworkingInteractorMock.swift | 36 ++++- .../ApproveEngineTests.swift | 43 +++--- .../Helpers/WCRequest+Extension.swift | 9 -- .../JsonRpcHistoryTests.swift | 84 ---------- .../Mocks/MockedRelayClient.swift | 78 +++++----- .../Mocks/NetworkingInteractorMock.swift | 102 ------------ .../Mocks/SerializerMock.swift | 34 ---- ...onControllerSessionStateMachineTests.swift | 16 +- .../PairEngineTests.swift | 6 +- .../PairingEngineTests.swift | 26 ++-- Tests/WalletConnectSignTests/Stub/Stubs.swift | 36 ++--- .../WalletConnectSignTests/WCRelayTests.swift | 145 +++++++++--------- .../WCResponseTests.swift | 16 +- 17 files changed, 206 insertions(+), 435 deletions(-) delete mode 100644 Tests/WalletConnectSignTests/Helpers/WCRequest+Extension.swift delete mode 100644 Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift delete mode 100644 Tests/WalletConnectSignTests/Mocks/NetworkingInteractorMock.swift delete mode 100644 Tests/WalletConnectSignTests/Mocks/SerializerMock.swift diff --git a/Package.swift b/Package.swift index a991ee8bf..4c925ebf6 100644 --- a/Package.swift +++ b/Package.swift @@ -52,7 +52,7 @@ let package = Package( path: "Sources/WalletConnectKMS"), .target( name: "WalletConnectPairing", - dependencies: ["WalletConnectUtils", "WalletConnectNetworking", "JSONRPC"]), + dependencies: ["WalletConnectNetworking"]), .target( name: "WalletConnectUtils", dependencies: ["Commons", "JSONRPC"]), diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index e29813c63..398e1566a 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -184,7 +184,7 @@ private extension ApproveEngine { } func respondError(payload: SubscriptionPayload, reason: ReasonCode, tag: Int) { - Task { + Task(priority: .high) { do { try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: tag, reason: reason) } catch { diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 60435dc0e..1372c8fe6 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -94,7 +94,7 @@ private extension SessionEngine { guard status == .connected else { return } sessionStore.getAll() .forEach { session in - Task { try await networkingInteractor.subscribe(topic: session.topic) } + Task(priority: .high) { try await networkingInteractor.subscribe(topic: session.topic) } } } .store(in: &publishers) @@ -142,7 +142,7 @@ private extension SessionEngine { } func respondError(payload: SubscriptionPayload, reason: ReasonCode, tag: Int) { - Task { + Task(priority: .high) { do { try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: tag, reason: reason) } catch { diff --git a/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift b/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift index 46ab1c516..7cca1066b 100644 --- a/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift +++ b/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift @@ -39,7 +39,7 @@ final class NonControllerSessionStateMachine { } private func respondError(payload: SubscriptionPayload, reason: ReasonCode, tag: Int) { - Task { + Task(priority: .high) { do { try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: tag, reason: reason) } catch { diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 7942fdac1..286390803 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -8,6 +8,19 @@ import WalletConnectNetworking public class NetworkingInteractorMock: NetworkInteracting { private(set) var subscriptions: [String] = [] + private(set) var unsubscriptions: [String] = [] + + private(set) var requests: [(topic: String, request: RPCRequest)] = [] + + private(set) var didRespondSuccess = false + private(set) var didRespondError = false + private(set) var didCallSubscribe = false + private(set) var didCallUnsubscribe = false + private(set) var didRespondOnTopic: String? + private(set) var lastErrorCode = -1 + + private(set) var requestCallCount = 0 + var didCallRequest: Bool { requestCallCount > 0 } public let socketConnectionStatusPublisherSubject = PassthroughSubject() public var socketConnectionStatusPublisher: AnyPublisher { @@ -67,33 +80,42 @@ public class NetworkingInteractorMock: NetworkInteracting { public func subscribe(topic: String) async throws { subscriptions.append(topic) + didCallSubscribe = true } func didSubscribe(to topic: String) -> Bool { - subscriptions.contains { $0 == topic } + subscriptions.contains { $0 == topic } } - public func unsubscribe(topic: String) { + func didUnsubscribe(to topic: String) -> Bool { + unsubscriptions.contains { $0 == topic } + } + public func unsubscribe(topic: String) { + unsubscriptions.append(topic) + didCallUnsubscribe = true } public func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws { - + requestCallCount += 1 + requests.append((topic, request)) } public func respond(topic: String, response: RPCResponse, tag: Int, envelopeType: Envelope.EnvelopeType) async throws { - + didRespondOnTopic = topic } public func respondSuccess(topic: String, requestId: RPCID, tag: Int, envelopeType: Envelope.EnvelopeType) async throws { - + didRespondSuccess = true } public func respondError(topic: String, requestId: RPCID, tag: Int, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws { - + lastErrorCode = reason.code + didRespondError = true } public func requestNetworkAck(_ request: RPCRequest, topic: String, tag: Int) async throws { - + requestCallCount += 1 + requests.append((topic, request)) } } diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index 88e7a541c..77fc29075 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -1,7 +1,9 @@ import XCTest import Combine +import JSONRPC import WalletConnectUtils import WalletConnectPairing +import WalletConnectNetworking @testable import WalletConnectSign @testable import TestingUtils @testable import WalletConnectKMS @@ -14,7 +16,7 @@ final class ApproveEngineTests: XCTestCase { var cryptoMock: KeyManagementServiceMock! var pairingStorageMock: WCPairingStorageMock! var sessionStorageMock: WCSessionStorageMock! - var proposalPayloadsStore: CodableStore! + var proposalPayloadsStore: CodableStore>! var publishers = Set() @@ -24,7 +26,7 @@ final class ApproveEngineTests: XCTestCase { cryptoMock = KeyManagementServiceMock() pairingStorageMock = WCPairingStorageMock() sessionStorageMock = WCSessionStorageMock() - proposalPayloadsStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") + proposalPayloadsStore = CodableStore>(defaults: RuntimeKeyValueStorage(), identifier: "") engine = ApproveEngine( networkingInteractor: networkingInteractor, proposalPayloadsStore: proposalPayloadsStore, @@ -52,9 +54,8 @@ final class ApproveEngineTests: XCTestCase { pairingStorageMock.setPairing(pairing) let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey) - let request = WCRequest(method: .sessionPropose, params: .sessionPropose(proposal)) - let payload = WCRequestSubscriptionPayload(topic: topicA, wcRequest: request) - networkingInteractor.wcRequestPublisherSubject.send(payload) + let request = RPCRequest(method: SignProtocolMethod.sessionPropose.method, params: proposal) + networkingInteractor.requestPublisherSubject.send((topicA, request)) try await engine.approveProposal(proposerPubKey: proposal.proposer.publicKey, validating: SessionNamespace.stubDictionary()) @@ -74,14 +75,13 @@ final class ApproveEngineTests: XCTestCase { var sessionProposed = false let proposerPubKey = AgreementPrivateKey().publicKey.hexRepresentation let proposal = SessionProposal.stub(proposerPubKey: proposerPubKey) - let request = WCRequest(method: .sessionPropose, params: .sessionPropose(proposal)) - let payload = WCRequestSubscriptionPayload(topic: topicA, wcRequest: request) + let request = RPCRequest(method: SignProtocolMethod.sessionPropose.method, params: proposal) engine.onSessionProposal = { _ in sessionProposed = true } - networkingInteractor.wcRequestPublisherSubject.send(payload) + networkingInteractor.requestPublisherSubject.send((topicA, request)) XCTAssertNotNil(try! proposalPayloadsStore.get(key: proposal.proposer.publicKey), "Proposer must store proposal payload") XCTAssertTrue(sessionProposed) } @@ -106,7 +106,9 @@ final class ApproveEngineTests: XCTestCase { } engine.settlingProposal = SessionProposal.stub() - networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubSettle(topic: sessionTopic)) + networkingInteractor.requestPublisherSubject.send((sessionTopic, RPCRequest.stubSettle())) + + usleep(100) XCTAssertTrue(sessionStorageMock.getSession(forTopic: sessionTopic)!.acknowledged, "Proposer must store acknowledged session on topic B") XCTAssertTrue(networkingInteractor.didRespondSuccess, "Proposer must send acknowledge on settle request") @@ -117,14 +119,10 @@ final class ApproveEngineTests: XCTestCase { let session = WCSession.stub(isSelfController: true, acknowledged: false) sessionStorageMock.setSession(session) - let settleResponse = JSONRPCResponse(id: 1, result: AnyCodable(true)) - let response = WCResponse( - topic: session.topic, - chainId: nil, - requestMethod: .sessionSettle, - requestParams: .sessionSettle(SessionType.SettleParams.stub()), - result: .response(settleResponse)) - networkingInteractor.responsePublisherSubject.send(response) + let request = RPCRequest(method: SignProtocolMethod.sessionSettle.method, params: SessionType.SettleParams.stub()) + let response = RPCResponse(matchingRequest: request, result: RPCResult.response(AnyCodable(true))) + + networkingInteractor.responsePublisherSubject.send((session.topic, request, response)) XCTAssertTrue(sessionStorageMock.getSession(forTopic: session.topic)!.acknowledged, "Responder must acknowledged session") } @@ -136,13 +134,10 @@ final class ApproveEngineTests: XCTestCase { cryptoMock.setAgreementSecret(AgreementKeys.stub(), topic: session.topic) try! cryptoMock.setPrivateKey(privateKey) - let response = WCResponse( - topic: session.topic, - chainId: nil, - requestMethod: .sessionSettle, - requestParams: .sessionSettle(SessionType.SettleParams.stub()), - result: .error(JSONRPCErrorResponse(id: 1, error: JSONRPCErrorResponse.Error(code: 0, message: "")))) - networkingInteractor.responsePublisherSubject.send(response) + let request = RPCRequest(method: SignProtocolMethod.sessionSettle.method, params: SessionType.SettleParams.stub()) + let response = RPCResponse(matchingRequest: request, result: RPCResult.error(JSONRPCError(code: 0, message: ""))) + + networkingInteractor.responsePublisherSubject.send((session.topic, request, response)) XCTAssertNil(sessionStorageMock.getSession(forTopic: session.topic), "Responder must remove session") XCTAssertTrue(networkingInteractor.didUnsubscribe(to: session.topic), "Responder must unsubscribe topic B") diff --git a/Tests/WalletConnectSignTests/Helpers/WCRequest+Extension.swift b/Tests/WalletConnectSignTests/Helpers/WCRequest+Extension.swift deleted file mode 100644 index d3616f365..000000000 --- a/Tests/WalletConnectSignTests/Helpers/WCRequest+Extension.swift +++ /dev/null @@ -1,9 +0,0 @@ -@testable import WalletConnectSign - -extension WCRequest { - - var sessionProposal: SessionProposal? { - guard case .sessionPropose(let proposal) = self.params else { return nil } - return proposal - } -} diff --git a/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift b/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift deleted file mode 100644 index dd98115f7..000000000 --- a/Tests/WalletConnectSignTests/JsonRpcHistoryTests.swift +++ /dev/null @@ -1,84 +0,0 @@ -import Foundation -import XCTest -import TestingUtils -import WalletConnectUtils -import WalletConnectPairing -@testable import WalletConnectSign - -final class JsonRpcHistoryTests: XCTestCase { - - var sut: WalletConnectSign.JsonRpcHistory! - - override func setUp() { - sut = JsonRpcHistory(logger: ConsoleLoggerMock(), keyValueStore: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "")) - } - - override func tearDown() { - sut = nil - } - - func testSetRecord() { - let recordinput = getTestJsonRpcRecordInput() - XCTAssertFalse(sut.exist(id: recordinput.request.id)) - try! sut.set(topic: recordinput.topic, request: recordinput.request) - XCTAssertTrue(sut.exist(id: recordinput.request.id)) - } - - func testGetRecord() { - let recordinput = getTestJsonRpcRecordInput() - XCTAssertNil(sut.get(id: recordinput.request.id)) - try! sut.set(topic: recordinput.topic, request: recordinput.request) - XCTAssertNotNil(sut.get(id: recordinput.request.id)) - } - - func testResolve() { - let recordinput = getTestJsonRpcRecordInput() - try! sut.set(topic: recordinput.topic, request: recordinput.request) - XCTAssertNil(sut.get(id: recordinput.request.id)?.response) - let jsonRpcResponse = JSONRPCResponse(id: recordinput.request.id, result: AnyCodable("")) - let response = JsonRpcResult.response(jsonRpcResponse) - _ = try! sut.resolve(response: response) - XCTAssertNotNil(sut.get(id: jsonRpcResponse.id)?.response) - } - - func testThrowsOnResolveDuplicate() { - let recordinput = getTestJsonRpcRecordInput() - try! sut.set(topic: recordinput.topic, request: recordinput.request) - let jsonRpcResponse = JSONRPCResponse(id: recordinput.request.id, result: AnyCodable("")) - let response = JsonRpcResult.response(jsonRpcResponse) - _ = try! sut.resolve(response: response) - XCTAssertThrowsError(try sut.resolve(response: response)) - } - - func testThrowsOnSetDuplicate() { - let recordinput = getTestJsonRpcRecordInput() - try! sut.set(topic: recordinput.topic, request: recordinput.request) - XCTAssertThrowsError(try sut.set(topic: recordinput.topic, request: recordinput.request)) - } - - func testDelete() { - let recordinput = getTestJsonRpcRecordInput() - try! sut.set(topic: recordinput.topic, request: recordinput.request) - XCTAssertNotNil(sut.get(id: recordinput.request.id)) - sut.delete(topic: testTopic) - XCTAssertNil(sut.get(id: recordinput.request.id)) - } - - func testGetPending() { - let recordinput1 = getTestJsonRpcRecordInput(id: 1) - let recordinput2 = getTestJsonRpcRecordInput(id: 2) - try! sut.set(topic: recordinput1.topic, request: recordinput1.request) - try! sut.set(topic: recordinput2.topic, request: recordinput2.request) - XCTAssertEqual(sut.getPending().count, 2) - let jsonRpcResponse = JSONRPCResponse(id: recordinput1.request.id, result: AnyCodable("")) - let response = JsonRpcResult.response(jsonRpcResponse) - _ = try! sut.resolve(response: response) - XCTAssertEqual(sut.getPending().count, 1) - } -} - -private let testTopic = "test_topic" -private func getTestJsonRpcRecordInput(id: Int64 = 0) -> (topic: String, request: WCRequest) { - let request = WCRequest(id: id, jsonrpc: "2.0", method: .pairingPing, params: WCRequest.Params.pairingPing(PairingType.PingParams())) - return (topic: testTopic, request: request) -} diff --git a/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift b/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift index eac4dacb5..a371a1e79 100644 --- a/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift +++ b/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift @@ -3,42 +3,42 @@ import Foundation @testable import WalletConnectRelay @testable import WalletConnectSign -class MockedRelayClient: NetworkRelaying { - - var messagePublisherSubject = PassthroughSubject<(topic: String, message: String), Never>() - var messagePublisher: AnyPublisher<(topic: String, message: String), Never> { - messagePublisherSubject.eraseToAnyPublisher() - } - - var socketConnectionStatusPublisherSubject = PassthroughSubject() - var socketConnectionStatusPublisher: AnyPublisher { - socketConnectionStatusPublisherSubject.eraseToAnyPublisher() - } - - var error: Error? - var prompt = false - - func publish(topic: String, payload: String, tag: Int, prompt: Bool) async throws { - self.prompt = prompt - } - - func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) { - self.prompt = prompt - onNetworkAcknowledge(error) - } - - func subscribe(topic: String) async throws {} - - func subscribe(topic: String, completion: @escaping (Error?) -> Void) { - } - - func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) { - } - - func connect() { - } - - func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) { - } - -} +//class MockedRelayClient: NetworkRelaying { +// +// var messagePublisherSubject = PassthroughSubject<(topic: String, message: String), Never>() +// var messagePublisher: AnyPublisher<(topic: String, message: String), Never> { +// messagePublisherSubject.eraseToAnyPublisher() +// } +// +// var socketConnectionStatusPublisherSubject = PassthroughSubject() +// var socketConnectionStatusPublisher: AnyPublisher { +// socketConnectionStatusPublisherSubject.eraseToAnyPublisher() +// } +// +// var error: Error? +// var prompt = false +// +// func publish(topic: String, payload: String, tag: Int, prompt: Bool) async throws { +// self.prompt = prompt +// } +// +// func publish(topic: String, payload: String, tag: Int, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) { +// self.prompt = prompt +// onNetworkAcknowledge(error) +// } +// +// func subscribe(topic: String) async throws {} +// +// func subscribe(topic: String, completion: @escaping (Error?) -> Void) { +// } +// +// func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) { +// } +// +// func connect() { +// } +// +// func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) { +// } +// +//} diff --git a/Tests/WalletConnectSignTests/Mocks/NetworkingInteractorMock.swift b/Tests/WalletConnectSignTests/Mocks/NetworkingInteractorMock.swift deleted file mode 100644 index a76d6ef91..000000000 --- a/Tests/WalletConnectSignTests/Mocks/NetworkingInteractorMock.swift +++ /dev/null @@ -1,102 +0,0 @@ -import Foundation -import Combine -import WalletConnectUtils -import WalletConnectPairing -@testable import WalletConnectSign -@testable import TestingUtils - -class NetworkingInteractorMock: NetworkInteracting { - - private(set) var subscriptions: [String] = [] - private(set) var unsubscriptions: [String] = [] - - let transportConnectionPublisherSubject = PassthroughSubject() - let responsePublisherSubject = PassthroughSubject() - let wcRequestPublisherSubject = PassthroughSubject() - - var transportConnectionPublisher: AnyPublisher { - transportConnectionPublisherSubject.eraseToAnyPublisher() - } - var wcRequestPublisher: AnyPublisher { - wcRequestPublisherSubject.eraseToAnyPublisher() - } - var responsePublisher: AnyPublisher { - responsePublisherSubject.eraseToAnyPublisher() - } - - var didCallSubscribe = false - var didRespondOnTopic: String? - var didCallUnsubscribe = false - var didRespondSuccess = false - var didRespondError = false - var lastErrorCode = -1 - var error: Error? - - private(set) var requestCallCount = 0 - var didCallRequest: Bool { requestCallCount > 0 } - - private(set) var requests: [(topic: String, request: WCRequest)] = [] - - func request(topic: String, payload: WCRequest) async throws { - requestCallCount += 1 - requests.append((topic, payload)) - } - - func requestNetworkAck(_ wcMethod: WCMethod, onTopic topic: String, completion: @escaping ((Error?) -> Void)) { - requestCallCount += 1 - requests.append((topic, wcMethod.asRequest())) - completion(nil) - } - - func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) { - requestCallCount += 1 - requests.append((topic, wcMethod.asRequest())) - } - - func respond(topic: String, response: JsonRpcResult, completion: @escaping ((Error?) -> Void)) { - didRespondOnTopic = topic - completion(error) - } - - func respond(topic: String, response: JsonRpcResult, tag: Int) async throws { - didRespondOnTopic = topic - } - - func respondSuccess(payload: WCRequestSubscriptionPayload) async throws { - respondSuccess(for: payload) - } - - func respondError(payload: WCRequestSubscriptionPayload, reason: ReasonCode) async throws { - lastErrorCode = reason.code - didRespondError = true - } - - func respondSuccess(for payload: WCRequestSubscriptionPayload) { - didRespondSuccess = true - } - - func subscribe(topic: String) { - subscriptions.append(topic) - didCallSubscribe = true - } - - func unsubscribe(topic: String) { - unsubscriptions.append(topic) - didCallUnsubscribe = true - } - - func sendSubscriptionPayloadOn(topic: String) { - let payload = WCRequestSubscriptionPayload(topic: topic, wcRequest: pingRequest) - wcRequestPublisherSubject.send(payload) - } - - func didSubscribe(to topic: String) -> Bool { - subscriptions.contains { $0 == topic } - } - - func didUnsubscribe(to topic: String) -> Bool { - unsubscriptions.contains { $0 == topic } - } -} - -private let pingRequest = WCRequest(id: 1, jsonrpc: "2.0", method: .pairingPing, params: WCRequest.Params.pairingPing(PairingType.PingParams())) diff --git a/Tests/WalletConnectSignTests/Mocks/SerializerMock.swift b/Tests/WalletConnectSignTests/Mocks/SerializerMock.swift deleted file mode 100644 index 339da3ff8..000000000 --- a/Tests/WalletConnectSignTests/Mocks/SerializerMock.swift +++ /dev/null @@ -1,34 +0,0 @@ -// - -import Foundation -import WalletConnectUtils -@testable import WalletConnectKMS -@testable import WalletConnectSign - -class SerializerMock: Serializing { - var deserialized: Any! - var serialized: String = "" - - func serialize(topic: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType) throws -> String { - try serialize(json: try encodable.json(), agreementKeys: AgreementKeys.stub()) - } - func deserialize(topic: String, encodedEnvelope: String) throws -> T { - return try deserialize(message: encodedEnvelope, symmetricKey: Data()) - } - func deserializeJsonRpc(topic: String, message: String) throws -> Result, JSONRPCErrorResponse> { - .success(try deserialize(message: message, symmetricKey: Data())) - } - - func deserialize(message: String, symmetricKey: Data) throws -> T where T: Codable { - if let deserializedModel = deserialized as? T { - return deserializedModel - } else { - throw NSError.mock() - } - } - - func serialize(json: String, agreementKeys: AgreementKeys) throws -> String { - return serialized - } - -} diff --git a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift index 47937bd42..8107cfd0d 100644 --- a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift +++ b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift @@ -2,6 +2,7 @@ import XCTest import WalletConnectUtils @testable import TestingUtils import WalletConnectKMS +import JSONRPC @testable import WalletConnectSign class NonControllerSessionStateMachineTests: XCTestCase { @@ -34,8 +35,9 @@ class NonControllerSessionStateMachineTests: XCTestCase { didCallbackUpdatMethods = true XCTAssertEqual(topic, session.topic) } - networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubUpdateNamespaces(topic: session.topic)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces())) XCTAssertTrue(didCallbackUpdatMethods) + usleep(100) XCTAssertTrue(networkingInteractor.didRespondSuccess) } @@ -49,7 +51,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { // } func testUpdateMethodPeerErrorSessionNotFound() { - networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubUpdateNamespaces(topic: "")) + networkingInteractor.requestPublisherSubject.send(("", RPCRequest.stubUpdateNamespaces())) usleep(100) XCTAssertFalse(networkingInteractor.didRespondSuccess) XCTAssertEqual(networkingInteractor.lastErrorCode, 7001) @@ -58,7 +60,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { func testUpdateMethodPeerErrorUnauthorized() { let session = WCSession.stub(isSelfController: true) // Peer is not a controller storageMock.setSession(session) - networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubUpdateNamespaces(topic: session.topic)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateNamespaces())) usleep(100) XCTAssertFalse(networkingInteractor.didRespondSuccess) XCTAssertEqual(networkingInteractor.lastErrorCode, 3003) @@ -72,7 +74,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let twoDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970) - networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubUpdateExpiry(topic: session.topic, expiry: twoDaysFromNowTimestamp)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp))) let extendedSession = storageMock.getAll().first {$0.topic == session.topic}! print(extendedSession.expiryDate) @@ -85,7 +87,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let twoDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970) - networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubUpdateExpiry(topic: session.topic, expiry: twoDaysFromNowTimestamp)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp))) let potentiallyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentiallyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended for peer non controller request ") @@ -96,7 +98,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { let session = WCSession.stub(isSelfController: false, expiryDate: tomorrow) storageMock.setSession(session) let tenDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 10).timeIntervalSince1970) - networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubUpdateExpiry(topic: session.topic, expiry: tenDaysFromNowTimestamp)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: tenDaysFromNowTimestamp))) let potentaillyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentaillyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended despite ttl to high") @@ -108,7 +110,7 @@ class NonControllerSessionStateMachineTests: XCTestCase { storageMock.setSession(session) let oneDayFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 10).timeIntervalSince1970) - networkingInteractor.wcRequestPublisherSubject.send(WCRequestSubscriptionPayload.stubUpdateExpiry(topic: session.topic, expiry: oneDayFromNowTimestamp)) + networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: oneDayFromNowTimestamp))) let potentaillyExtendedSession = storageMock.getAll().first {$0.topic == session.topic}! XCTAssertEqual(potentaillyExtendedSession.expiryDate.timeIntervalSinceReferenceDate, tomorrow.timeIntervalSinceReferenceDate, accuracy: 1, "expiry date has been extended despite ttl to low") } diff --git a/Tests/WalletConnectSignTests/PairEngineTests.swift b/Tests/WalletConnectSignTests/PairEngineTests.swift index 9b581672d..66018ff7b 100644 --- a/Tests/WalletConnectSignTests/PairEngineTests.swift +++ b/Tests/WalletConnectSignTests/PairEngineTests.swift @@ -3,6 +3,7 @@ import XCTest @testable import TestingUtils @testable import WalletConnectKMS import WalletConnectUtils +import WalletConnectNetworking final class PairEngineTests: XCTestCase { @@ -11,16 +12,11 @@ final class PairEngineTests: XCTestCase { var networkingInteractor: NetworkingInteractorMock! var storageMock: WCPairingStorageMock! var cryptoMock: KeyManagementServiceMock! - var proposalPayloadsStore: CodableStore! - - var topicGenerator: TopicGenerator! override func setUp() { networkingInteractor = NetworkingInteractorMock() storageMock = WCPairingStorageMock() cryptoMock = KeyManagementServiceMock() - topicGenerator = TopicGenerator() - proposalPayloadsStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") setupEngine() } diff --git a/Tests/WalletConnectSignTests/PairingEngineTests.swift b/Tests/WalletConnectSignTests/PairingEngineTests.swift index 785be6dc2..6bd6538cc 100644 --- a/Tests/WalletConnectSignTests/PairingEngineTests.swift +++ b/Tests/WalletConnectSignTests/PairingEngineTests.swift @@ -1,5 +1,6 @@ import XCTest import Combine +import JSONRPC @testable import WalletConnectSign @testable import TestingUtils @testable import WalletConnectKMS @@ -78,7 +79,7 @@ final class PairingEngineTests: XCTestCase { try! await engine.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions) guard let publishTopic = networkingInteractor.requests.first?.topic, - let proposal = networkingInteractor.requests.first?.request.sessionProposal else { + let proposal = try? networkingInteractor.requests.first?.request.params?.get(SessionType.ProposeParams.self) else { XCTFail("Proposer must publish a proposal request."); return } XCTAssert(cryptoMock.hasPrivateKey(for: proposal.proposer.publicKey), "Proposer must store the private key matching the public key sent through the proposal.") @@ -96,7 +97,7 @@ final class PairingEngineTests: XCTestCase { try! await engine.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions) guard let request = networkingInteractor.requests.first?.request, - let proposal = networkingInteractor.requests.first?.request.sessionProposal else { + let proposal = try? networkingInteractor.requests.first?.request.params?.get(SessionType.ProposeParams.self) else { XCTFail("Proposer must publish session proposal request"); return } @@ -104,14 +105,9 @@ final class PairingEngineTests: XCTestCase { let responder = Participant.stub() let proposalResponse = SessionType.ProposeResponse(relay: relayOptions, responderPublicKey: responder.publicKey) - let jsonRpcResponse = JSONRPCResponse(id: request.id, result: AnyCodable.decoded(proposalResponse)) - let response = WCResponse(topic: topicA, - chainId: nil, - requestMethod: request.method, - requestParams: request.params, - result: .response(jsonRpcResponse)) + let response = RPCResponse(id: request.id!, result: RPCResult.response(AnyCodable(proposalResponse))) - networkingInteractor.responsePublisherSubject.send(response) + networkingInteractor.responsePublisherSubject.send((topicA, request, response)) let privateKey = try! cryptoMock.getPrivateKey(for: proposal.proposer.publicKey)! let topicB = deriveTopic(publicKey: responder.publicKey, privateKey: privateKey) let storedPairing = storageMock.getPairing(forTopic: topicA)! @@ -133,12 +129,12 @@ final class PairingEngineTests: XCTestCase { try! await engine.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions) guard let request = networkingInteractor.requests.first?.request, - let proposal = networkingInteractor.requests.first?.request.sessionProposal else { + let proposal = try? networkingInteractor.requests.first?.request.params?.get(SessionType.ProposeParams.self) else { XCTFail("Proposer must publish session proposal request"); return } - let response = WCResponse.stubError(forRequest: request, topic: topicA) - networkingInteractor.responsePublisherSubject.send(response) + let response = RPCResponse.stubError(forRequest: request) + networkingInteractor.responsePublisherSubject.send((topicA, request, response)) XCTAssert(networkingInteractor.didUnsubscribe(to: pairing.topic), "Proposer must unsubscribe if pairing is inactive.") XCTAssertFalse(storageMock.hasPairing(forTopic: pairing.topic), "Proposer must delete an inactive pairing.") @@ -157,7 +153,7 @@ final class PairingEngineTests: XCTestCase { try? await engine.propose(pairingTopic: pairing.topic, namespaces: ProposalNamespace.stubDictionary(), relay: relayOptions) guard let request = networkingInteractor.requests.first?.request, - let proposal = networkingInteractor.requests.first?.request.sessionProposal else { + let proposal = try? networkingInteractor.requests.first?.request.params?.get(SessionType.ProposeParams.self) else { XCTFail("Proposer must publish session proposal request"); return } @@ -165,8 +161,8 @@ final class PairingEngineTests: XCTestCase { storedPairing.activate() storageMock.setPairing(storedPairing) - let response = WCResponse.stubError(forRequest: request, topic: topicA) - networkingInteractor.responsePublisherSubject.send(response) + let response = RPCResponse.stubError(forRequest: request) + networkingInteractor.responsePublisherSubject.send((topicA, request, response)) XCTAssertFalse(networkingInteractor.didUnsubscribe(to: pairing.topic), "Proposer must not unsubscribe if pairing is active.") XCTAssert(storageMock.hasPairing(forTopic: pairing.topic), "Proposer must not delete an active pairing.") diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift index a17ad98a8..96ea5eb3b 100644 --- a/Tests/WalletConnectSignTests/Stub/Stubs.swift +++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift @@ -1,5 +1,6 @@ @testable import WalletConnectSign import Foundation +import JSONRPC import WalletConnectKMS import WalletConnectUtils import TestingUtils @@ -59,29 +60,25 @@ extension AgreementPeer { } } -extension WCRequestSubscriptionPayload { +extension RPCRequest { - static func stubUpdateNamespaces(topic: String, namespaces: [String: SessionNamespace] = SessionNamespace.stubDictionary()) -> WCRequestSubscriptionPayload { - let updateMethod = WCMethod.wcSessionUpdate(SessionType.UpdateParams(namespaces: namespaces)).asRequest() - return WCRequestSubscriptionPayload(topic: topic, wcRequest: updateMethod) + static func stubUpdateNamespaces(namespaces: [String: SessionNamespace] = SessionNamespace.stubDictionary()) -> RPCRequest { + return RPCRequest(method: SignProtocolMethod.sessionUpdate.method, params: SessionType.UpdateParams(namespaces: namespaces)) } - static func stubUpdateExpiry(topic: String, expiry: Int64) -> WCRequestSubscriptionPayload { - let updateExpiryMethod = WCMethod.wcSessionExtend(SessionType.UpdateExpiryParams(expiry: expiry)).asRequest() - return WCRequestSubscriptionPayload(topic: topic, wcRequest: updateExpiryMethod) + static func stubUpdateExpiry(expiry: Int64) -> RPCRequest { + return RPCRequest(method: SignProtocolMethod.sessionExtend.method, params: SessionType.UpdateExpiryParams(expiry: expiry)) } - static func stubSettle(topic: String) -> WCRequestSubscriptionPayload { - let method = WCMethod.wcSessionSettle(SessionType.SettleParams.stub()) - return WCRequestSubscriptionPayload(topic: topic, wcRequest: method.asRequest()) + static func stubSettle() -> RPCRequest { + return RPCRequest(method: SignProtocolMethod.sessionSettle.method, params: SessionType.SettleParams.stub()) } - static func stubRequest(topic: String, method: String, chainId: Blockchain) -> WCRequestSubscriptionPayload { + static func stubRequest(method: String, chainId: Blockchain) -> RPCRequest { let params = SessionType.RequestParams( request: SessionType.RequestParams.Request(method: method, params: AnyCodable(EmptyCodable())), chainId: chainId) - let request = WCRequest(method: .sessionRequest, params: .sessionRequest(params)) - return WCRequestSubscriptionPayload(topic: topic, wcRequest: request) + return RPCRequest(method: SignProtocolMethod.sessionRequest.method, params: params) } } @@ -95,15 +92,8 @@ extension SessionProposal { } } -extension WCResponse { - static func stubError(forRequest request: WCRequest, topic: String) -> WCResponse { - let errorResponse = JSONRPCErrorResponse(id: request.id, error: JSONRPCErrorResponse.Error(code: 0, message: "")) - return WCResponse( - topic: topic, - chainId: nil, - requestMethod: request.method, - requestParams: request.params, - result: .error(errorResponse) - ) +extension RPCResponse { + static func stubError(forRequest request: RPCRequest) -> RPCResponse { + return RPCResponse(matchingRequest: request, result: RPCResult.error(JSONRPCError(code: 0, message: ""))) } } diff --git a/Tests/WalletConnectSignTests/WCRelayTests.swift b/Tests/WalletConnectSignTests/WCRelayTests.swift index a42fbd328..aea8037c9 100644 --- a/Tests/WalletConnectSignTests/WCRelayTests.swift +++ b/Tests/WalletConnectSignTests/WCRelayTests.swift @@ -1,72 +1,73 @@ -import Foundation -import Combine -import XCTest -import WalletConnectUtils -import WalletConnectPairing -@testable import TestingUtils -@testable import WalletConnectSign - -class NetworkingInteractorTests: XCTestCase { - var networkingInteractor: NetworkInteractor! - var relayClient: MockedRelayClient! - var serializer: SerializerMock! - - private var publishers = [AnyCancellable]() - - override func setUp() { - let logger = ConsoleLoggerMock() - serializer = SerializerMock() - relayClient = MockedRelayClient() - networkingInteractor = NetworkInteractor(relayClient: relayClient, serializer: serializer, logger: logger, jsonRpcHistory: JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""))) - } - - override func tearDown() { - networkingInteractor = nil - relayClient = nil - serializer = nil - } - - func testNotifiesOnEncryptedWCJsonRpcRequest() { - let requestExpectation = expectation(description: "notifies with request") - let topic = "fefc3dc39cacbc562ed58f92b296e2d65a6b07ef08992b93db5b3cb86280635a" - networkingInteractor.wcRequestPublisher.sink { (_) in - requestExpectation.fulfill() - }.store(in: &publishers) - serializer.deserialized = request - relayClient.messagePublisherSubject.send((topic, testPayload)) - waitForExpectations(timeout: 1.001, handler: nil) - } - - func testPromptOnSessionRequest() async { - let topic = "fefc3dc39cacbc562ed58f92b296e2d65a6b07ef08992b93db5b3cb86280635a" - let method = getWCSessionMethod() - relayClient.prompt = false - try! await networkingInteractor.request(topic: topic, payload: method.asRequest()) - XCTAssertTrue(relayClient.prompt) - } -} - -extension NetworkingInteractorTests { - func getWCSessionMethod() -> WCMethod { - let sessionRequestParams = SessionType.RequestParams(request: SessionType.RequestParams.Request(method: "method", params: AnyCodable("params")), chainId: Blockchain("eip155:1")!) - return .wcSessionRequest(sessionRequestParams) - } -} - -private let testPayload = -""" -{ - "id":1630300527198334, - "jsonrpc":"2.0", - "method":"irn_subscription", - "params":{ - "id":"0847f4e1dd19cf03a43dc7525f39896b630e9da33e4683c8efbc92ea671b5e07", - "data":{ - "topic":"fefc3dc39cacbc562ed58f92b296e2d65a6b07ef08992b93db5b3cb86280635a", - "message":"7b226964223a313633303330303532383030302c226a736f6e727063223a22322e30222c22726573756c74223a747275657d" - } - } -} -""" -// TODO - change for different request -private let request = WCRequest(id: 1, jsonrpc: "2.0", method: .pairingPing, params: WCRequest.Params.pairingPing(PairingType.PingParams())) +//import Foundation +//import Combine +//import XCTest +//import WalletConnectUtils +//import WalletConnectPairing +//import WalletConnectNetworking +//@testable import TestingUtils +//@testable import WalletConnectSign +// +//class NetworkingInteractorTests: XCTestCase { +// var networkingInteractor: NetworkingInteractor! +// var relayClient: MockedRelayClient! +// var serializer: SerializerMock! +// +// private var publishers = [AnyCancellable]() +// +// override func setUp() { +// let logger = ConsoleLoggerMock() +// serializer = SerializerMock() +// relayClient = MockedRelayClient() +// networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: RPCHistory(logger: logger, keyValueStore: CodableStore(defaults: RuntimeKeyValueStorage(), identifier: ""))) +// } +// +// override func tearDown() { +// networkingInteractor = nil +// relayClient = nil +// serializer = nil +// } +// +// func testNotifiesOnEncryptedWCJsonRpcRequest() { +// let requestExpectation = expectation(description: "notifies with request") +// let topic = "fefc3dc39cacbc562ed58f92b296e2d65a6b07ef08992b93db5b3cb86280635a" +// networkingInteractor.wcRequestPublisher.sink { (_) in +// requestExpectation.fulfill() +// }.store(in: &publishers) +// serializer.deserialized = request +// relayClient.messagePublisherSubject.send((topic, testPayload)) +// waitForExpectations(timeout: 1.001, handler: nil) +// } +// +// func testPromptOnSessionRequest() async { +// let topic = "fefc3dc39cacbc562ed58f92b296e2d65a6b07ef08992b93db5b3cb86280635a" +// let method = getWCSessionMethod() +// relayClient.prompt = false +// try! await networkingInteractor.request(topic: topic, payload: method.asRequest()) +// XCTAssertTrue(relayClient.prompt) +// } +//} +// +//extension NetworkingInteractorTests { +// func getWCSessionMethod() -> WCMethod { +// let sessionRequestParams = SessionType.RequestParams(request: SessionType.RequestParams.Request(method: "method", params: AnyCodable("params")), chainId: Blockchain("eip155:1")!) +// return .wcSessionRequest(sessionRequestParams) +// } +//} +// +//private let testPayload = +//""" +//{ +// "id":1630300527198334, +// "jsonrpc":"2.0", +// "method":"irn_subscription", +// "params":{ +// "id":"0847f4e1dd19cf03a43dc7525f39896b630e9da33e4683c8efbc92ea671b5e07", +// "data":{ +// "topic":"fefc3dc39cacbc562ed58f92b296e2d65a6b07ef08992b93db5b3cb86280635a", +// "message":"7b226964223a313633303330303532383030302c226a736f6e727063223a22322e30222c22726573756c74223a747275657d" +// } +// } +//} +//""" +//// TODO - change for different request +//private let request = WCRequest(id: 1, jsonrpc: "2.0", method: .pairingPing, params: WCRequest.Params.pairingPing(PairingType.PingParams())) diff --git a/Tests/WalletConnectSignTests/WCResponseTests.swift b/Tests/WalletConnectSignTests/WCResponseTests.swift index 24729924b..b4d814d9e 100644 --- a/Tests/WalletConnectSignTests/WCResponseTests.swift +++ b/Tests/WalletConnectSignTests/WCResponseTests.swift @@ -1,17 +1,15 @@ import XCTest +import JSONRPC @testable import WalletConnectSign -final class WCResponseTests: XCTestCase { +final class RPCIDTests: XCTestCase { func testTimestamp() { - let request = WCRequest( - method: .pairingPing, - params: .pairingPing(.init()) - ) - let response = WCResponse.stubError(forRequest: request, topic: "topic") - let timestamp = Date(timeIntervalSince1970: TimeInterval(request.id / 1000 / 1000)) + let request = RPCRequest(method: "method") + let response = RPCResponse(matchingRequest: request, error: JSONRPCError(code: 0, message: "message")) + let timestamp = Date(timeIntervalSince1970: TimeInterval(request.id!.right! / 1000 / 1000)) - XCTAssertEqual(response.timestamp, timestamp) - XCTAssertTrue(Calendar.current.isDateInToday(response.timestamp)) + XCTAssertEqual(response.id!.timestamp, timestamp) + XCTAssertTrue(Calendar.current.isDateInToday(response.id!.timestamp)) } } From 12587e4282a77d53c40123236573d88224e0bfcd Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 9 Sep 2022 14:15:16 +0300 Subject: [PATCH 10/40] Sample apps build errors fixed --- .../DApp/Sign/ResponseViewController.swift | 10 ++++----- .../SessionDetailViewController.swift | 13 +++++------ .../SessionDetailViewModel.swift | 20 +++++++++++------ .../Wallet/WalletViewController.swift | 13 +++++------ .../Sign/Helpers/ClientDelegate.swift | 11 +++------- .../Sign/SignClientTests.swift | 4 ++-- .../Engine/Common/SessionEngine.swift | 1 + Sources/WalletConnectSign/Response.swift | 1 + .../WalletConnectSign/Sign/SignClient.swift | 22 ++++++------------- 9 files changed, 42 insertions(+), 53 deletions(-) diff --git a/Example/DApp/Sign/ResponseViewController.swift b/Example/DApp/Sign/ResponseViewController.swift index 4f8d1c0b3..eec81065f 100644 --- a/Example/DApp/Sign/ResponseViewController.swift +++ b/Example/DApp/Sign/ResponseViewController.swift @@ -23,14 +23,14 @@ class ResponseViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - let record = Sign.instance.getSessionRequestRecord(id: response.result.id)! + let record = Sign.instance.getSessionRequestRecord(id: response.id)! switch response.result { case .response(let response): - responseView.nameLabel.text = "Received Response\n\(record.request.method)" - responseView.descriptionLabel.text = try! response.result.get(String.self).description + responseView.nameLabel.text = "Received Response\n\(record.method)" + responseView.descriptionLabel.text = try! response.get(String.self).description case .error(let error): - responseView.nameLabel.text = "Received Error\n\(record.request.method)" - responseView.descriptionLabel.text = error.error.message + responseView.nameLabel.text = "Received Error\n\(record.method)" + responseView.descriptionLabel.text = error.message } responseView.dismissButton.addTarget(self, action: #selector(dismissSelf), for: .touchUpInside) } diff --git a/Example/ExampleApp/SessionDetails/SessionDetailViewController.swift b/Example/ExampleApp/SessionDetails/SessionDetailViewController.swift index 4d7d6b39e..f1b2a05bc 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailViewController.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailViewController.swift @@ -24,8 +24,7 @@ final class SessionDetailViewController: UIHostingController let viewController = RequestViewController(request) viewController.onSign = { [unowned self] in let result = Signer.signEth(request: request) - let response = JSONRPCResponse(id: request.id, result: result) - respondOnSign(request: request, response: response) + respondOnSign(request: request, response: result) reload() } viewController.onReject = { [unowned self] in @@ -35,11 +34,11 @@ final class SessionDetailViewController: UIHostingController present(viewController, animated: true) } - private func respondOnSign(request: Request, response: JSONRPCResponse) { + private func respondOnSign(request: Request, response: AnyCodable) { print("[WALLET] Respond on Sign") Task { do { - try await Sign.instance.respond(topic: request.topic, response: .response(response)) + try await Sign.instance.respond(topic: request.topic, requestId: request.id, response: .response(response)) } catch { print("[DAPP] Respond Error: \(error.localizedDescription)") } @@ -52,10 +51,8 @@ final class SessionDetailViewController: UIHostingController do { try await Sign.instance.respond( topic: request.topic, - response: .error(JSONRPCErrorResponse( - id: request.id, - error: JSONRPCErrorResponse.Error(code: 0, message: "")) - ) + requestId: request.id, + response: .error(.init(code: 0, message: "")) ) } catch { print("[DAPP] Respond Error: \(error.localizedDescription)") diff --git a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift index f29dea255..88fe381b1 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift @@ -7,6 +7,8 @@ final class SessionDetailViewModel: ObservableObject { private let session: Session private let client: SignClient + private var publishers = Set() + enum Fields { case accounts case methods @@ -22,6 +24,8 @@ final class SessionDetailViewModel: ObservableObject { self.session = session self.client = client self.namespaces = session.namespaces + + setupSubscriptions() } var peerName: String { @@ -81,13 +85,8 @@ final class SessionDetailViewModel: ObservableObject { } func ping() { - client.ping(topic: session.topic) { result in - switch result { - case .success: - self.pingSuccess = true - case .failure: - self.pingFailed = true - } + Task(priority: .userInitiated) { + try await client.ping(topic: session.topic) } } @@ -98,6 +97,13 @@ final class SessionDetailViewModel: ObservableObject { private extension SessionDetailViewModel { + func setupSubscriptions() { + client.pingResponsePublisher.sink { _ in + self.pingSuccess = true + } + .store(in: &publishers) + } + func addTestAccount(for chain: String) { guard let viewModel = namespace(for: chain) else { return } diff --git a/Example/ExampleApp/Wallet/WalletViewController.swift b/Example/ExampleApp/Wallet/WalletViewController.swift index 9d574ea34..c3bbacdae 100644 --- a/Example/ExampleApp/Wallet/WalletViewController.swift +++ b/Example/ExampleApp/Wallet/WalletViewController.swift @@ -71,8 +71,7 @@ final class WalletViewController: UIViewController { let requestVC = RequestViewController(request) requestVC.onSign = { [unowned self] in let result = Signer.signEth(request: request) - let response = JSONRPCResponse(id: request.id, result: result) - respondOnSign(request: request, response: response) + respondOnSign(request: request, response: result) reloadSessionDetailsIfNeeded() } requestVC.onReject = { [unowned self] in @@ -90,11 +89,11 @@ final class WalletViewController: UIViewController { } @MainActor - private func respondOnSign(request: Request, response: JSONRPCResponse) { + private func respondOnSign(request: Request, response: AnyCodable) { print("[WALLET] Respond on Sign") Task { do { - try await Sign.instance.respond(topic: request.topic, response: .response(response)) + try await Sign.instance.respond(topic: request.topic, requestId: request.id, response: .response(response)) } catch { print("[DAPP] Respond Error: \(error.localizedDescription)") } @@ -108,10 +107,8 @@ final class WalletViewController: UIViewController { do { try await Sign.instance.respond( topic: request.topic, - response: .error(JSONRPCErrorResponse( - id: request.id, - error: JSONRPCErrorResponse.Error(code: 0, message: "")) - ) + requestId: request.id, + response: .error(.init(code: 0, message: "")) ) } catch { print("[DAPP] Respond Error: \(error.localizedDescription)") diff --git a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift index ab7b43e11..e299d0316 100644 --- a/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift +++ b/Example/IntegrationTests/Sign/Helpers/ClientDelegate.swift @@ -14,8 +14,7 @@ class ClientDelegate { var onSessionDelete: (() -> Void)? var onSessionUpdateNamespaces: ((String, [String: SessionNamespace]) -> Void)? var onSessionExtend: ((String, Date) -> Void)? - var onSessionPing: ((String) -> Void)? - var onPairingPing: ((String) -> Void)? + var onPing: ((String) -> Void)? var onEventReceived: ((Session.Event, String) -> Void)? private var publishers = Set() @@ -66,12 +65,8 @@ class ClientDelegate { self.onSessionExtend?(topic, date) }.store(in: &publishers) - client.sessionPingResponsePublisher.sink { topic in - self.onSessionPing?(topic) - }.store(in: &publishers) - - client.pairingPingResponsePublisher.sink { topic in - self.onPairingPing?(topic) + client.pingResponsePublisher.sink { topic in + self.onPing?(topic) }.store(in: &publishers) } } diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 8a30c72cc..17524d541 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -145,7 +145,7 @@ final class SignClientTests: XCTestCase { let pairing = wallet.client.getPairings().first! - wallet.onPairingPing = { topic in + wallet.onPing = { topic in XCTAssertEqual(topic, pairing.topic) pongResponseExpectation.fulfill() } @@ -173,7 +173,7 @@ final class SignClientTests: XCTestCase { } } - dapp.onSessionPing = { topic in + dapp.onPing = { topic in let session = self.wallet.client.getSessions().first! XCTAssertEqual(topic, session.topic) expectation.fulfill() diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 1372c8fe6..e210a3444 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -126,6 +126,7 @@ private extension SessionEngine { networkingInteractor.responseSubscription(on: SignProtocolMethod.sessionRequest) .sink { [unowned self] (payload: ResponseSubscriptionPayload) in onSessionResponse?(Response( + id: payload.id, topic: payload.topic, chainId: payload.request.chainId.absoluteString, result: payload.response diff --git a/Sources/WalletConnectSign/Response.swift b/Sources/WalletConnectSign/Response.swift index c01e438e5..aaaaf02fa 100644 --- a/Sources/WalletConnectSign/Response.swift +++ b/Sources/WalletConnectSign/Response.swift @@ -3,6 +3,7 @@ import JSONRPC import WalletConnectUtils public struct Response: Codable { + public let id: RPCID public let topic: String public let chainId: String? public let result: RPCResult diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 4d343153a..83ba5cbb0 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -87,15 +87,8 @@ public final class SignClient { /// Publisher that sends session topic when session ping received /// /// Event will be emited on controller and non-controller clients. - public var sessionPingResponsePublisher: AnyPublisher { - sessionPingResponsePublisherSubject.eraseToAnyPublisher() - } - - /// Publisher that sends pairing topic when pairing ping received - /// - /// Event will be emited on controller and non-controller clients. - public var pairingPingResponsePublisher: AnyPublisher { - pairingPingResponsePublisherSubject.eraseToAnyPublisher() + public var pingResponsePublisher: AnyPublisher { + pingResponsePublisherSubject.eraseToAnyPublisher() } /// An object that loggs SDK's errors and info messages @@ -126,8 +119,7 @@ public final class SignClient { private let sessionUpdatePublisherSubject = PassthroughSubject<(sessionTopic: String, namespaces: [String: SessionNamespace]), Never>() private let sessionEventPublisherSubject = PassthroughSubject<(event: Session.Event, sessionTopic: String, chainId: Blockchain?), Never>() private let sessionExtendPublisherSubject = PassthroughSubject<(sessionTopic: String, date: Date), Never>() - private let sessionPingResponsePublisherSubject = PassthroughSubject() - private let pairingPingResponsePublisherSubject = PassthroughSubject() + private let pingResponsePublisherSubject = PassthroughSubject() private var publishers = Set() @@ -324,9 +316,9 @@ public final class SignClient { /// - Parameter id: id of a wc_sessionRequest jsonrpc request /// - Returns: json rpc record object for given id or nil if record for give id does not exits - public func getSessionRequestRecord(id: Int64) -> Request? { + public func getSessionRequestRecord(id: RPCID) -> Request? { guard - let record = history.get(recordId: RPCID(id)), + let record = history.get(recordId: id), let request = try? record.request.params?.get(SessionType.RequestParams.self) else { return nil } @@ -379,10 +371,10 @@ public final class SignClient { sessionResponsePublisherSubject.send(response) } pairingPingService.onResponse = { [unowned self] topic in - pairingPingResponsePublisherSubject.send(topic) + pingResponsePublisherSubject.send(topic) } sessionPingService.onResponse = { [unowned self] topic in - sessionPingResponsePublisherSubject.send(topic) + pingResponsePublisherSubject.send(topic) } } From fb9805bb49379b3c705bad3d6ccdf7ec98486fbd Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 12 Sep 2022 14:10:06 +0300 Subject: [PATCH 11/40] Sample apps and UI tests repaired --- .../SelectChain/SelectChainViewController.swift | 4 ---- Example/DApp/Sign/SignCoordinator.swift | 10 +++++++--- Example/ExampleApp/SceneDelegate.swift | 17 ++++++++++++++++- .../SessionDetails/SessionDetailViewModel.swift | 10 ++++++---- .../SessionDetailsViewController.swift | 8 ++++---- .../Wallet/WalletViewController.swift | 4 +--- .../Engine/Common/PairingEngine.swift | 2 +- 7 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift index 74210f740..3a78d7efc 100644 --- a/Example/DApp/Sign/SelectChain/SelectChainViewController.swift +++ b/Example/DApp/Sign/SelectChain/SelectChainViewController.swift @@ -15,16 +15,12 @@ class SelectChainViewController: UIViewController, UITableViewDataSource { private var publishers = [AnyCancellable]() let chains = [Chain(name: "Ethereum", id: "eip155:1"), Chain(name: "Polygon", id: "eip155:137")] - var onSessionSettled: ((Session) -> Void)? override func viewDidLoad() { super.viewDidLoad() navigationItem.title = "Available Chains" selectChainView.tableView.dataSource = self selectChainView.connectButton.addTarget(self, action: #selector(connect), for: .touchUpInside) selectChainView.openWallet.addTarget(self, action: #selector(openWallet), for: .touchUpInside) - Sign.instance.sessionSettlePublisher.sink {[unowned self] session in - onSessionSettled?(session) - }.store(in: &publishers) } override func loadView() { diff --git a/Example/DApp/Sign/SignCoordinator.swift b/Example/DApp/Sign/SignCoordinator.swift index 1373e4c30..eec11715c 100644 --- a/Example/DApp/Sign/SignCoordinator.swift +++ b/Example/DApp/Sign/SignCoordinator.swift @@ -44,6 +44,12 @@ final class SignCoordinator { presentResponse(for: response) }.store(in: &publishers) + Sign.instance.sessionSettlePublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] session in + showAccountsScreen(session) + }.store(in: &publishers) + if let session = Sign.instance.getSessions().first { showAccountsScreen(session) } else { @@ -53,9 +59,6 @@ final class SignCoordinator { private func showSelectChainScreen() { let controller = SelectChainViewController() - controller.onSessionSettled = { [unowned self] session in - showAccountsScreen(session) - } navigationController.viewControllers = [controller] } @@ -64,6 +67,7 @@ final class SignCoordinator { controller.onDisconnect = { [unowned self] in showSelectChainScreen() } + navigationController.presentedViewController?.dismiss(animated: true) navigationController.viewControllers = [controller] } diff --git a/Example/ExampleApp/SceneDelegate.swift b/Example/ExampleApp/SceneDelegate.swift index ba57b9ff4..6cd39be9a 100644 --- a/Example/ExampleApp/SceneDelegate.swift +++ b/Example/ExampleApp/SceneDelegate.swift @@ -1,4 +1,5 @@ import UIKit +import Combine import WalletConnectSign import WalletConnectRelay import Starscream @@ -15,6 +16,10 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? + private var publishers: Set = [] + private var onConnected: (() -> Void)? + private var connectionStatus: SocketConnectionStatus = .disconnected + func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let metadata = AppMetadata( @@ -35,6 +40,16 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) window?.rootViewController = UITabBarController.createExampleApp() window?.makeKeyAndVisible() + + Sign.instance.socketConnectionStatusPublisher + .receive(on: DispatchQueue.main) + .sink { [unowned self] status in + self.connectionStatus = status + if status == .connected { + self.onConnected?() + self.onConnected = nil + } + }.store(in: &publishers) } func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { @@ -44,7 +59,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { } let wcUri = incomingURL.absoluteString.deletingPrefix("https://walletconnect.com/wc?uri=") let vc = ((window!.rootViewController as! UINavigationController).viewControllers[0] as! WalletViewController) - Task(priority: .high) {try? await Sign.instance.pair(uri: WalletConnectURI(string: wcUri)!)} + Task(priority: .high) { try? await Sign.instance.pair(uri: WalletConnectURI(string: wcUri)!) } vc.onClientConnected = { Task(priority: .high) { do { diff --git a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift index 88fe381b1..d43a5ce3d 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift @@ -98,10 +98,12 @@ final class SessionDetailViewModel: ObservableObject { private extension SessionDetailViewModel { func setupSubscriptions() { - client.pingResponsePublisher.sink { _ in - self.pingSuccess = true - } - .store(in: &publishers) + client.pingResponsePublisher + .receive(on: DispatchQueue.main) + .sink { _ in + self.pingSuccess = true + } + .store(in: &publishers) } func addTestAccount(for chain: String) { diff --git a/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift b/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift index f53ecbdef..bb6d21be6 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailsViewController.swift @@ -49,11 +49,11 @@ final class SessionDetailsViewController: UIViewController, UITableViewDelegate, @objc private func ping() { - Sign.instance.ping(topic: session.topic) { result in - switch result { - case .success: + Task(priority: .userInitiated) { @MainActor in + do { + try await Sign.instance.ping(topic: session.topic) print("received ping response") - case .failure(let error): + } catch { print(error) } } diff --git a/Example/ExampleApp/Wallet/WalletViewController.swift b/Example/ExampleApp/Wallet/WalletViewController.swift index c3bbacdae..fbc5322db 100644 --- a/Example/ExampleApp/Wallet/WalletViewController.swift +++ b/Example/ExampleApp/Wallet/WalletViewController.swift @@ -10,7 +10,6 @@ final class WalletViewController: UIViewController { lazy var account = Signer.privateKey.address.hex(eip55: true) var sessionItems: [ActiveSessionItem] = [] var currentProposal: Session.Proposal? - var onClientConnected: (() -> Void)? private var publishers = [AnyCancellable]() private let walletView: WalletView = { @@ -236,9 +235,8 @@ extension WalletViewController { func setUpAuthSubscribing() { Sign.instance.socketConnectionStatusPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] status in + .sink { status in if status == .connected { - self?.onClientConnected?() print("Client connected") } }.store(in: &publishers) diff --git a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift index 7c75d9876..cac8290f8 100644 --- a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift @@ -73,7 +73,7 @@ final class PairingEngine { requiredNamespaces: namespaces) let request = RPCRequest(method: SignProtocolMethod.sessionPropose.method, params: proposal) - try await networkingInteractor.request(request, topic: pairingTopic, tag: SignProtocolMethod.sessionPropose.requestTag) + try await networkingInteractor.requestNetworkAck(request, topic: pairingTopic, tag: SignProtocolMethod.sessionPropose.requestTag) } } From f2dc91eb4edbd3c85972955937fcdb9250bb0ccc Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Mon, 12 Sep 2022 21:08:51 +0300 Subject: [PATCH 12/40] testSessionPing duplicate removed --- .../Sign/SignClientTests.swift | 21 ------------------- 1 file changed, 21 deletions(-) diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 17524d541..a62321a05 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -314,27 +314,6 @@ final class SignClientTests: XCTestCase { } - func testSessionPing() async { - let expectation = expectation(description: "Dapp receives ping response") - let requiredNamespaces = ProposalNamespace.stubRequired() - let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) - - wallet.onSessionProposal = { [unowned self] proposal in - Task(priority: .high) { - try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) - } - } - dapp.onSessionSettled = { [unowned self] settledSession in - dapp.client.ping(topic: settledSession.topic) {_ in - expectation.fulfill() - } - } - let uri = try! await dapp.client.connect(requiredNamespaces: requiredNamespaces) - try! await wallet.client.pair(uri: uri!) - wait(for: [expectation], timeout: defaultTimeout) - } - - func testSuccessfulSessionUpdateNamespaces() async { let expectation = expectation(description: "Dapp updates namespaces") let requiredNamespaces = ProposalNamespace.stubRequired() From dc469c250c774cbece96949b36e0a0fe0ae5221c Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 13 Sep 2022 02:35:07 +0300 Subject: [PATCH 13/40] Wallet sample app fixed --- Example/DApp/Sign/SignCoordinator.swift | 2 +- Example/ExampleApp/SceneDelegate.swift | 15 +-------------- .../Engine/Common/PairingEngine.swift | 1 - .../Engine/Common/SessionEngine.swift | 1 - 4 files changed, 2 insertions(+), 17 deletions(-) diff --git a/Example/DApp/Sign/SignCoordinator.swift b/Example/DApp/Sign/SignCoordinator.swift index eec11715c..8470ad200 100644 --- a/Example/DApp/Sign/SignCoordinator.swift +++ b/Example/DApp/Sign/SignCoordinator.swift @@ -67,7 +67,7 @@ final class SignCoordinator { controller.onDisconnect = { [unowned self] in showSelectChainScreen() } - navigationController.presentedViewController?.dismiss(animated: true) + navigationController.presentedViewController?.dismiss(animated: false) navigationController.viewControllers = [controller] } diff --git a/Example/ExampleApp/SceneDelegate.swift b/Example/ExampleApp/SceneDelegate.swift index 6cd39be9a..d469490bc 100644 --- a/Example/ExampleApp/SceneDelegate.swift +++ b/Example/ExampleApp/SceneDelegate.swift @@ -1,4 +1,5 @@ import UIKit +import Foundation import Combine import WalletConnectSign import WalletConnectRelay @@ -16,10 +17,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? - private var publishers: Set = [] - private var onConnected: (() -> Void)? - private var connectionStatus: SocketConnectionStatus = .disconnected - func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { let metadata = AppMetadata( @@ -40,16 +37,6 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { window = UIWindow(windowScene: windowScene) window?.rootViewController = UITabBarController.createExampleApp() window?.makeKeyAndVisible() - - Sign.instance.socketConnectionStatusPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] status in - self.connectionStatus = status - if status == .connected { - self.onConnected?() - self.onConnected = nil - } - }.store(in: &publishers) } func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { diff --git a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift index cac8290f8..d6a012de0 100644 --- a/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/PairingEngine.swift @@ -84,7 +84,6 @@ private extension PairingEngine { func setupNetworkingSubscriptions() { networkingInteractor.socketConnectionStatusPublisher .sink { [unowned self] status in - guard status == .connected else { return } pairingStore.getAll() .forEach { pairing in Task(priority: .high) { try await networkingInteractor.subscribe(topic: pairing.topic) } diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index e210a3444..2c9c2be07 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -91,7 +91,6 @@ private extension SessionEngine { func setupConnectionSubscriptions() { networkingInteractor.socketConnectionStatusPublisher .sink { [unowned self] status in - guard status == .connected else { return } sessionStore.getAll() .forEach { session in Task(priority: .high) { try await networkingInteractor.subscribe(topic: session.topic) } From be2641ed93993e63d06d9b58cbd28879262070d0 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 13 Sep 2022 16:06:32 +0300 Subject: [PATCH 14/40] UI Tests improvements --- .../ExampleApp/Wallet/WalletViewController.swift | 5 ++++- Example/UITests/Engine/WalletEngine.swift | 8 ++++++++ Example/UITests/Regression/RegressionTests.swift | 15 ++++++++------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/Example/ExampleApp/Wallet/WalletViewController.swift b/Example/ExampleApp/Wallet/WalletViewController.swift index fbc5322db..d53343480 100644 --- a/Example/ExampleApp/Wallet/WalletViewController.swift +++ b/Example/ExampleApp/Wallet/WalletViewController.swift @@ -12,6 +12,8 @@ final class WalletViewController: UIViewController { var currentProposal: Session.Proposal? private var publishers = [AnyCancellable]() + var onClientConnected: (() -> Void)? + private let walletView: WalletView = { WalletView() }() @@ -235,9 +237,10 @@ extension WalletViewController { func setUpAuthSubscribing() { Sign.instance.socketConnectionStatusPublisher .receive(on: DispatchQueue.main) - .sink { status in + .sink { [weak self] status in if status == .connected { print("Client connected") + self?.onClientConnected?() } }.store(in: &publishers) diff --git a/Example/UITests/Engine/WalletEngine.swift b/Example/UITests/Engine/WalletEngine.swift index 536e45443..2255d87b8 100644 --- a/Example/UITests/Engine/WalletEngine.swift +++ b/Example/UITests/Engine/WalletEngine.swift @@ -45,7 +45,15 @@ struct WalletEngine { instance.buttons["Ping"] } + var okButton: XCUIElement { + instance.buttons["OK"] + } + var pingAlert: XCUIElement { instance.alerts.element.staticTexts["Received ping response"] } + + func swipeDismiss() { + instance.swipeDown(velocity: .fast) + } } diff --git a/Example/UITests/Regression/RegressionTests.swift b/Example/UITests/Regression/RegressionTests.swift index 716ba8702..4d3016b60 100644 --- a/Example/UITests/Regression/RegressionTests.swift +++ b/Example/UITests/Regression/RegressionTests.swift @@ -4,15 +4,13 @@ class PairingTests: XCTestCase { private let engine: Engine = Engine() - private static var cleanLaunch: Bool = true - - override func setUp() { - engine.routing.launch(app: .dapp, clean: PairingTests.cleanLaunch) - engine.routing.launch(app: .wallet, clean: PairingTests.cleanLaunch) - - PairingTests.cleanLaunch = false + override class func setUp() { + let engine: Engine = Engine() + engine.routing.launch(app: .dapp, clean: true) + engine.routing.launch(app: .wallet, clean: true) } + /// Check pairing proposal approval via QR code or uri /// - TU001 func test01PairingCreation() { @@ -45,6 +43,9 @@ class PairingTests: XCTestCase { engine.wallet.pingButton.waitTap() XCTAssertTrue(engine.wallet.pingAlert.waitExists()) + + engine.wallet.okButton.waitTap() + engine.wallet.swipeDismiss() } /// Approve session on existing pairing From 48d3adcdbd49e6c9ab4564a59cb6e0d9749120ae Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 14 Sep 2022 21:18:11 +0300 Subject: [PATCH 15/40] Approve Engine RPCResult replaced --- .../Engine/Common/ApproveEngine.swift | 116 ++++++++++-------- 1 file changed, 62 insertions(+), 54 deletions(-) diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 398e1566a..5a23eb079 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -12,6 +12,7 @@ final class ApproveEngine { case relayNotFound case proposalPayloadsNotFound case pairingNotFound + case sessionNotFound case agreementMissingOrInvalid } @@ -53,6 +54,7 @@ final class ApproveEngine { setupRequestSubscriptions() setupResponseSubscriptions() + setupResponseErrorSubscriptions() } func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace]) async throws { @@ -165,21 +167,25 @@ private extension ApproveEngine { func setupResponseSubscriptions() { networkingInteractor.responseSubscription(on: SignProtocolMethod.sessionPropose) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in handleSessionProposeResponse(payload: payload) }.store(in: &publishers) networkingInteractor.responseSubscription(on: SignProtocolMethod.sessionSettle) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in handleSessionSettleResponse(payload: payload) }.store(in: &publishers) + } + func setupResponseErrorSubscriptions() { networkingInteractor.responseErrorSubscription(on: SignProtocolMethod.sessionPropose) .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in - onSessionRejected?( - payload.request.publicRepresentation(), - SessionType.Reason(code: payload.error.code, message: payload.error.message) - ) + handleSessionProposeResponseError(payload: payload) + }.store(in: &publishers) + + networkingInteractor.responseErrorSubscription(on: SignProtocolMethod.sessionSettle) + .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in + handleSessionSettleResponseError(payload: payload) }.store(in: &publishers) } @@ -201,32 +207,14 @@ private extension ApproveEngine { // MARK: SessionProposeResponse // TODO: Move to Non-Controller SettleEngine - func handleSessionProposeResponse(payload: ResponseSubscriptionPayload) { + func handleSessionProposeResponse(payload: ResponseSubscriptionPayload) { do { - let sessionTopic = try handleProposeResponse( - pairingTopic: payload.topic, - proposal: payload.request, - result: payload.response - ) - settlingProposal = payload.request + let pairingTopic = payload.topic - Task(priority: .high) { - try await networkingInteractor.subscribe(topic: sessionTopic) - } - } catch { - guard let error = error as? Reason else { - return logger.debug(error.localizedDescription) + guard var pairing = pairingStore.getPairing(forTopic: pairingTopic) else { + throw Errors.pairingNotFound } - onSessionRejected?(payload.request.publicRepresentation(), SessionType.Reason(code: error.code, message: error.message)) - } - } - func handleProposeResponse(pairingTopic: String, proposal: SessionProposal, result: RPCResult) throws -> String { - guard var pairing = pairingStore.getPairing(forTopic: pairingTopic) - else { throw Errors.pairingNotFound } - - switch result { - case .response(let response): // Activate the pairing if !pairing.active { pairing.activate() @@ -236,9 +224,8 @@ private extension ApproveEngine { pairingStore.setPairing(pairing) - let selfPublicKey = try AgreementPublicKey(hex: proposal.proposer.publicKey) - let proposeResponse = try response.get(SessionType.ProposeResponse.self) - let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPublicKey, peerPublicKey: proposeResponse.responderPublicKey) + let selfPublicKey = try AgreementPublicKey(hex: payload.request.proposer.publicKey) + let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPublicKey, peerPublicKey: payload.response.responderPublicKey) let sessionTopic = agreementKeys.derivedTopic() logger.debug("Received Session Proposal response") @@ -246,37 +233,57 @@ private extension ApproveEngine { try kms.setAgreementSecret(agreementKeys, topic: sessionTopic) sessionToPairingTopic.set(pairingTopic, forKey: sessionTopic) - return sessionTopic + settlingProposal = payload.request - case .error(let error): - if !pairing.active { - kms.deleteSymmetricKey(for: pairing.topic) - networkingInteractor.unsubscribe(topic: pairing.topic) - pairingStore.delete(topic: pairingTopic) + Task(priority: .high) { + try await networkingInteractor.subscribe(topic: sessionTopic) } - logger.debug("Session Proposal has been rejected") - kms.deletePrivateKey(for: proposal.proposer.publicKey) - throw error + } catch { + return logger.debug(error.localizedDescription) + } + } + + func handleSessionProposeResponseError(payload: ResponseSubscriptionErrorPayload) { + guard let pairing = pairingStore.getPairing(forTopic: payload.topic) else { + return logger.debug(Errors.pairingNotFound.localizedDescription) } + + if !pairing.active { + kms.deleteSymmetricKey(for: pairing.topic) + networkingInteractor.unsubscribe(topic: pairing.topic) + pairingStore.delete(topic: payload.topic) + } + logger.debug("Session Proposal has been rejected") + kms.deletePrivateKey(for: payload.request.proposer.publicKey) + + onSessionRejected?( + payload.request.publicRepresentation(), + SessionType.Reason(code: payload.error.code, message: payload.error.message) + ) } // MARK: SessionSettleResponse - func handleSessionSettleResponse(payload: ResponseSubscriptionPayload) { - guard let session = sessionStore.getSession(forTopic: payload.topic) else { return } - switch payload.response { - case .response: - logger.debug("Received session settle response") - guard var session = sessionStore.getSession(forTopic: payload.topic) else { return } - session.acknowledge() - sessionStore.setSession(session) - case .error(let error): - logger.error("Error - session rejected, Reason: \(error)") - networkingInteractor.unsubscribe(topic: payload.topic) - sessionStore.delete(topic: payload.topic) - kms.deleteAgreementSecret(for: payload.topic) - kms.deletePrivateKey(for: session.publicKey!) + func handleSessionSettleResponse(payload: ResponseSubscriptionPayload) { + guard var session = sessionStore.getSession(forTopic: payload.topic) else { + return logger.debug(Errors.sessionNotFound.localizedDescription) } + + logger.debug("Received session settle response") + session.acknowledge() + sessionStore.setSession(session) + } + + func handleSessionSettleResponseError(payload: ResponseSubscriptionErrorPayload) { + guard let session = sessionStore.getSession(forTopic: payload.topic) else { + return logger.debug(Errors.sessionNotFound.localizedDescription) + } + + logger.error("Error - session rejected, Reason: \(payload.error)") + networkingInteractor.unsubscribe(topic: payload.topic) + sessionStore.delete(topic: payload.topic) + kms.deleteAgreementSecret(for: payload.topic) + kms.deletePrivateKey(for: session.publicKey!) } // MARK: SessionProposeRequest @@ -292,6 +299,7 @@ private extension ApproveEngine { } // MARK: SessionSettleRequest + func handleSessionSettleRequest(payload: RequestSubscriptionPayload) { logger.debug("Did receive session settle request") From 707d335a4ee6993caab201c00858eeec1bce64a6 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 14 Sep 2022 21:25:35 +0300 Subject: [PATCH 16/40] Prompt --- .../WalletConnectNetworking/NetworkInteractor.swift | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectNetworking/NetworkInteractor.swift b/Sources/WalletConnectNetworking/NetworkInteractor.swift index 405533ef1..111d0003f 100644 --- a/Sources/WalletConnectNetworking/NetworkInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkInteractor.swift @@ -96,7 +96,7 @@ public class NetworkingInteractor: NetworkInteracting { public func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws { try rpcHistory.set(request, forTopic: topic, emmitedBy: .local) let message = try! serializer.serialize(topic: topic, encodable: request, envelopeType: envelopeType) - try await relayClient.publish(topic: topic, payload: message, tag: tag) + try await relayClient.publish(topic: topic, payload: message, tag: tag, prompt: shouldPrompt(method: request.method)) } /// Completes with an acknowledgement from the relay network. @@ -107,7 +107,7 @@ public class NetworkingInteractor: NetworkInteracting { try rpcHistory.set(request, forTopic: topic, emmitedBy: .local) let message = try serializer.serialize(topic: topic, encodable: request) return try await withCheckedThrowingContinuation { continuation in - relayClient.publish(topic: topic, payload: message, tag: tag) { error in + relayClient.publish(topic: topic, payload: message, tag: tag, prompt: shouldPrompt(method: request.method)) { error in if let error = error { continuation.resume(throwing: error) } else { @@ -165,4 +165,13 @@ public class NetworkingInteractor: NetworkInteracting { logger.debug("Handle json rpc response error: \(error)") } } + + private func shouldPrompt(method: String) -> Bool { + switch method { + case "wc_sessionRequest": // TODO: Include promt in ProtocolMethod + return true + default: + return false + } + } } From f79460583131cf91793b42a42c2d4449c1cc5432 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Wed, 14 Sep 2022 23:38:34 +0300 Subject: [PATCH 17/40] Unit test fixed --- Tests/WalletConnectSignTests/ApproveEngineTests.swift | 2 +- Tests/WalletConnectSignTests/Stub/Stubs.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index 77fc29075..3f2c39da8 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -135,7 +135,7 @@ final class ApproveEngineTests: XCTestCase { try! cryptoMock.setPrivateKey(privateKey) let request = RPCRequest(method: SignProtocolMethod.sessionSettle.method, params: SessionType.SettleParams.stub()) - let response = RPCResponse(matchingRequest: request, result: RPCResult.error(JSONRPCError(code: 0, message: ""))) + let response = RPCResponse.stubError(forRequest: request) networkingInteractor.responsePublisherSubject.send((session.topic, request, response)) diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift index 96ea5eb3b..f0b87a080 100644 --- a/Tests/WalletConnectSignTests/Stub/Stubs.swift +++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift @@ -94,6 +94,6 @@ extension SessionProposal { extension RPCResponse { static func stubError(forRequest request: RPCRequest) -> RPCResponse { - return RPCResponse(matchingRequest: request, result: RPCResult.error(JSONRPCError(code: 0, message: ""))) + return RPCResponse(matchingRequest: request, error: JSONRPCError(code: 0, message: "")) } } From ffbfc0d7041770b4ad42abfcd4d93934db9f397c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 19 Sep 2022 15:44:55 +0200 Subject: [PATCH 18/40] pass pairing api integration test --- Example/ExampleApp.xcodeproj/project.pbxproj | 12 +++ .../Pairing/PairingTests.swift | 85 +++++++++++++++++++ .../Push/PushClient.swift | 5 +- .../Push/PushRequestSubscriber.swift | 4 +- 4 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 Example/IntegrationTests/Pairing/PairingTests.swift diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index e23637099..645d75745 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ 84CE644E279ED2FF00142511 /* SelectChainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE644D279ED2FF00142511 /* SelectChainView.swift */; }; 84CE6452279ED42B00142511 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE6451279ED42B00142511 /* ConnectView.swift */; }; 84CE645527A29D4D00142511 /* ResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE645427A29D4C00142511 /* ResponseViewController.swift */; }; + 84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CEC64528D89D6B00D081A8 /* PairingTests.swift */; }; 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D2A66528A4F51E0088AE09 /* AuthTests.swift */; }; 84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */; }; 84F568C2279582D200D0A289 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C1279582D200D0A289 /* Signer.swift */; }; @@ -235,6 +236,7 @@ 84CE6451279ED42B00142511 /* ConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectView.swift; sourceTree = ""; }; 84CE6453279FFE1100142511 /* Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Wallet.entitlements; sourceTree = ""; }; 84CE645427A29D4C00142511 /* ResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseViewController.swift; sourceTree = ""; }; + 84CEC64528D89D6B00D081A8 /* PairingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PairingTests.swift; sourceTree = ""; }; 84D2A66528A4F51E0088AE09 /* AuthTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthTests.swift; sourceTree = ""; }; 84F568C1279582D200D0A289 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = ""; }; 84F568C32795832A00D0A289 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; @@ -581,6 +583,14 @@ path = Connect; sourceTree = ""; }; + 84CEC64728D8A98900D081A8 /* Pairing */ = { + isa = PBXGroup; + children = ( + 84CEC64528D89D6B00D081A8 /* PairingTests.swift */, + ); + path = Pairing; + sourceTree = ""; + }; 84D2A66728A4F5260088AE09 /* Auth */ = { isa = PBXGroup; children = ( @@ -1019,6 +1029,7 @@ A5E03DEE286464DB00888481 /* IntegrationTests */ = { isa = PBXGroup; children = ( + 84CEC64728D8A98900D081A8 /* Pairing */, A5E03E0B28646AA500888481 /* Relay */, A5E03E0A28646A8A00888481 /* Stubs */, A5E03E0928646A8100888481 /* Sign */, @@ -1455,6 +1466,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 84CEC64628D89D6B00D081A8 /* PairingTests.swift in Sources */, 767DC83528997F8E00080FA9 /* EthSendTransaction.swift in Sources */, A5E03E03286466F400888481 /* ChatTests.swift in Sources */, 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */, diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift new file mode 100644 index 000000000..fb4123393 --- /dev/null +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -0,0 +1,85 @@ +import Foundation +import XCTest +import WalletConnectUtils +@testable import WalletConnectKMS +import WalletConnectRelay +import Combine +import WalletConnectNetworking +import WalletConnectPairing + + +final class PairingTests: XCTestCase { + var appPairingClient: PairingClient! + var walletPairingClient: PairingClient! + + private var publishers = [AnyCancellable]() + + override func setUp() { + appPairingClient = makeClient(prefix: "👻 App") + walletPairingClient = makeClient(prefix: "🤑 Wallet") + + let expectation = expectation(description: "Wait Clients Connected") + expectation.expectedFulfillmentCount = 2 + + appPairingClient.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + walletPairingClient.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + wait(for: [expectation], timeout: 5) + } + + + func makeClient(prefix: String) -> PairingClient { + let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) + let projectId = "3ca2919724fbfa5456a25194e369a8b4" + let keychain = KeychainStorageMock() + let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) + + let pairingClient = PairingClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) + return pairingClient + } + + func makePushClient(suffix: String) -> PushClient { + let logger = ConsoleLogger(suffix: suffix, loggingLevel: .debug) + let projectId = "3ca2919724fbfa5456a25194e369a8b4" + let keychain = KeychainStorageMock() + let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) + return PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) + } + + func testProposePushOnPairing() async { + let exp = expectation(description: "") + + let appPushClient = makePushClient(suffix: "👻 App") + let walletPushClient = makePushClient(suffix: "🤑 Wallet") + + walletPushClient.proposalPublisher.sink { _ in + exp.fulfill() + }.store(in: &publishers) + + appPairingClient.configure(with: [appPushClient]) + + walletPairingClient.configure(with: [walletPushClient]) + + let uri = try! await appPairingClient.create() + + try! await walletPairingClient.pair(uri: uri) + + try! await appPushClient.propose(topic: uri.topic) + + + + wait(for: [exp], timeout: 2) + + } + +} + diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index 15164aa79..6bf0fa2da 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -4,6 +4,8 @@ import WalletConnectUtils import WalletConnectNetworking import Combine +struct ProposalParams: Codable {} + public class PushClient: Paringable { public var protocolMethod: ProtocolMethod @@ -33,10 +35,11 @@ public class PushClient: Paringable { func handleProposal() { pairingRequestSubscriber.onRequest = { [unowned self] _ in logger.debug("Push: received proposal") + proposalPublisherSubject.send("done") } } public func propose(topic: String) async throws { - try await pairingRequester.request(topic: topic) + try await pairingRequester.request(topic: topic, params: AnyCodable(PushRequestParams())) } } diff --git a/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift b/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift index d0f04445f..6d9681e7b 100644 --- a/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift +++ b/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift @@ -53,8 +53,8 @@ public class PairingRequester { self.protocolMethod = protocolMethod } - func request(topic: String) async throws { - let request = RPCRequest(method: protocolMethod.method, params: AnyCodable("")) + func request(topic: String, params: AnyCodable) async throws { + let request = RPCRequest(method: protocolMethod.method, params: params) try await networkingInteractor.requestNetworkAck(request, topic: topic, tag: PushProtocolMethod.propose.requestTag) } From c2ba26c6546e28f11c30ebeb1ea320226267e95d Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 20 Sep 2022 09:05:20 +0200 Subject: [PATCH 19/40] refactor pairing --- .../Pairing/PairingTests.swift | 4 +- .../WalletConnectPairing/PairingClient.swift | 15 ++++--- .../PairingClientFactory.swift | 27 +++++++++++ .../Push/PairingClientFactory.swift | 45 ------------------- .../Push/PushClient.swift | 2 - .../Push/PushClientFactory.swift | 22 +++++++++ .../PairingRequestSubscriber.swift} | 28 ------------ .../Common/Paringable/PairingRequester.swift | 30 +++++++++++++ .../Common/Paringable/Paringable.swift | 9 ++++ .../Ping}/PairingPingService.swift | 0 .../{ => Common/Ping}/PingRequester.swift | 0 .../{ => Common/Ping}/PingResponder.swift | 0 .../Ping}/PingResponseSubscriber.swift | 0 .../{ => Types}/PairingProtocolMethod.swift | 0 14 files changed, 100 insertions(+), 82 deletions(-) create mode 100644 Sources/WalletConnectPairing/PairingClientFactory.swift delete mode 100644 Sources/WalletConnectPairing/Push/PairingClientFactory.swift create mode 100644 Sources/WalletConnectPairing/Push/PushClientFactory.swift rename Sources/WalletConnectPairing/{Push/PushRequestSubscriber.swift => Services/Common/Paringable/PairingRequestSubscriber.swift} (50%) create mode 100644 Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequester.swift create mode 100644 Sources/WalletConnectPairing/Services/Common/Paringable/Paringable.swift rename Sources/WalletConnectPairing/Services/{ => Common/Ping}/PairingPingService.swift (100%) rename Sources/WalletConnectPairing/Services/{ => Common/Ping}/PingRequester.swift (100%) rename Sources/WalletConnectPairing/Services/{ => Common/Ping}/PingResponder.swift (100%) rename Sources/WalletConnectPairing/Services/{ => Common/Ping}/PingResponseSubscriber.swift (100%) rename Sources/WalletConnectPairing/{ => Types}/PairingProtocolMethod.swift (100%) diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index fb4123393..3887c7ca8 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -65,9 +65,9 @@ final class PairingTests: XCTestCase { exp.fulfill() }.store(in: &publishers) - appPairingClient.configure(with: [appPushClient]) + appPairingClient.configureProtocols(with: [appPushClient]) - walletPairingClient.configure(with: [walletPushClient]) + walletPairingClient.configureProtocols(with: [walletPushClient]) let uri = try! await appPairingClient.create() diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 10a760784..2d3f21e6c 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -1,6 +1,7 @@ import Foundation import WalletConnectUtils import WalletConnectRelay +import WalletConnectNetworking import Combine public class PairingClient { @@ -8,13 +9,17 @@ public class PairingClient { private let appPairService: AppPairService public let socketConnectionStatusPublisher: AnyPublisher let logger: ConsoleLogging + private let networkingInteractor: NetworkInteracting + init(appPairService: AppPairService, + networkingInteractor: NetworkInteracting, logger: ConsoleLogging, walletPairService: WalletPairService, socketConnectionStatusPublisher: AnyPublisher ) { self.appPairService = appPairService self.walletPairService = walletPairService + self.networkingInteractor = networkingInteractor self.socketConnectionStatusPublisher = socketConnectionStatusPublisher self.logger = logger } @@ -33,13 +38,13 @@ public class PairingClient { return try await appPairService.create() } - public func configure(with paringables: [Paringable]) { - var p = paringables.first! - - p.pairingRequestSubscriber = PairingRequestSubscriber(networkingInteractor: walletPairService.networkingInteractor, logger: logger, kms: walletPairService.kms, protocolMethod: p.protocolMethod) + public func configureProtocols(with paringables: [Paringable]) { + paringables.forEach { + $0.pairingRequestSubscriber = PairingRequestSubscriber(networkingInteractor: walletPairService.networkingInteractor, logger: logger, kms: walletPairService.kms, protocolMethod: $0.protocolMethod) + $0.pairingRequester = PairingRequester(networkingInteractor: walletPairService.networkingInteractor, kms: walletPairService.kms, logger: logger, protocolMethod: $0.protocolMethod) + } - p.pairingRequester = PairingRequester(networkingInteractor: walletPairService.networkingInteractor, kms: walletPairService.kms, logger: logger, protocolMethod: p.protocolMethod) } diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift new file mode 100644 index 000000000..efccb6579 --- /dev/null +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -0,0 +1,27 @@ +import Foundation +import WalletConnectRelay +import WalletConnectUtils +import WalletConnectKMS +import WalletConnectNetworking + +public struct PairingClientFactory { + public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PairingClient { + let kms = KeyManagementService(keychain: keychainStorage) + let serializer = Serializer(kms: kms) + let kv = RuntimeKeyValueStorage() + let historyStorage = CodableStore(defaults: kv, identifier: "") + let history = RPCHistory(keyValueStore: historyStorage) + + + let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) + let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: kv, identifier: ""))) + + + let appPairService = AppPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) + + let walletPaS = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) + + return PairingClient(appPairService: appPairService, networkingInteractor: networkingInteractor, logger: logger, walletPairService: walletPaS, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher) + } +} + diff --git a/Sources/WalletConnectPairing/Push/PairingClientFactory.swift b/Sources/WalletConnectPairing/Push/PairingClientFactory.swift deleted file mode 100644 index 8410ee0a3..000000000 --- a/Sources/WalletConnectPairing/Push/PairingClientFactory.swift +++ /dev/null @@ -1,45 +0,0 @@ -import Foundation -import WalletConnectRelay -import WalletConnectUtils -import WalletConnectKMS -import WalletConnectNetworking - -public struct PairingClientFactory { - public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PairingClient { - let kms = KeyManagementService(keychain: keychainStorage) - let serializer = Serializer(kms: kms) - let kv = RuntimeKeyValueStorage() - let historyStorage = CodableStore(defaults: kv, identifier: "") - let history = RPCHistory(keyValueStore: historyStorage) - - - let networkingInt = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) - let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: kv, identifier: ""))) - - - let appPairService = AppPairService(networkingInteractor: networkingInt, kms: kms, pairingStorage: pairingStore) - - let walletPaS = WalletPairService(networkingInteractor: networkingInt, kms: kms, pairingStorage: pairingStore) - - return PairingClient(appPairService: appPairService, logger: logger, walletPairService: walletPaS, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher) - } -} - - - -public struct PushClientFactory { - public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PushClient { - let kms = KeyManagementService(keychain: keychainStorage) - let serializer = Serializer(kms: kms) - let kv = RuntimeKeyValueStorage() - let historyStorage = CodableStore(defaults: kv, identifier: "") - let history = RPCHistory(keyValueStore: historyStorage) - - - let networkingInt = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) - - - - return PushClient(networkingInteractor: networkingInt, logger: logger, kms: kms) - } -} diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index 6bf0fa2da..4f5da3438 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -4,8 +4,6 @@ import WalletConnectUtils import WalletConnectNetworking import Combine -struct ProposalParams: Codable {} - public class PushClient: Paringable { public var protocolMethod: ProtocolMethod diff --git a/Sources/WalletConnectPairing/Push/PushClientFactory.swift b/Sources/WalletConnectPairing/Push/PushClientFactory.swift new file mode 100644 index 000000000..0348185ba --- /dev/null +++ b/Sources/WalletConnectPairing/Push/PushClientFactory.swift @@ -0,0 +1,22 @@ +import Foundation +import WalletConnectRelay +import WalletConnectUtils +import WalletConnectKMS +import WalletConnectNetworking + +public struct PushClientFactory { + public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PushClient { + let kms = KeyManagementService(keychain: keychainStorage) + let serializer = Serializer(kms: kms) + let kv = RuntimeKeyValueStorage() + let historyStorage = CodableStore(defaults: kv, identifier: "") + let history = RPCHistory(keyValueStore: historyStorage) + + + let networkingInt = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) + + + + return PushClient(networkingInteractor: networkingInt, logger: logger, kms: kms) + } +} diff --git a/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift b/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequestSubscriber.swift similarity index 50% rename from Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift rename to Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequestSubscriber.swift index 6d9681e7b..ad5eac2f7 100644 --- a/Sources/WalletConnectPairing/Push/PushRequestSubscriber.swift +++ b/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequestSubscriber.swift @@ -5,11 +5,6 @@ import WalletConnectUtils import WalletConnectKMS import WalletConnectNetworking -public protocol Paringable { - var protocolMethod: ProtocolMethod { get set } - var pairingRequestSubscriber: PairingRequestSubscriber! {get set} - var pairingRequester: PairingRequester! {get set} -} public class PairingRequestSubscriber { private let networkingInteractor: NetworkInteracting @@ -36,26 +31,3 @@ public class PairingRequestSubscriber { }.store(in: &publishers) } } - -public class PairingRequester { - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private let logger: ConsoleLogging - let protocolMethod: ProtocolMethod - - init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, - logger: ConsoleLogging, - protocolMethod: ProtocolMethod) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.logger = logger - self.protocolMethod = protocolMethod - } - - func request(topic: String, params: AnyCodable) async throws { - let request = RPCRequest(method: protocolMethod.method, params: params) - - try await networkingInteractor.requestNetworkAck(request, topic: topic, tag: PushProtocolMethod.propose.requestTag) - } -} diff --git a/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequester.swift b/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequester.swift new file mode 100644 index 000000000..c92a64d73 --- /dev/null +++ b/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequester.swift @@ -0,0 +1,30 @@ +import Foundation +import Combine +import JSONRPC +import WalletConnectUtils +import WalletConnectKMS +import WalletConnectNetworking + + +public class PairingRequester { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private let logger: ConsoleLogging + let protocolMethod: ProtocolMethod + + init(networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + logger: ConsoleLogging, + protocolMethod: ProtocolMethod) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.logger = logger + self.protocolMethod = protocolMethod + } + + func request(topic: String, params: AnyCodable) async throws { + let request = RPCRequest(method: protocolMethod.method, params: params) + + try await networkingInteractor.requestNetworkAck(request, topic: topic, tag: PushProtocolMethod.propose.requestTag) + } +} diff --git a/Sources/WalletConnectPairing/Services/Common/Paringable/Paringable.swift b/Sources/WalletConnectPairing/Services/Common/Paringable/Paringable.swift new file mode 100644 index 000000000..c1580e37d --- /dev/null +++ b/Sources/WalletConnectPairing/Services/Common/Paringable/Paringable.swift @@ -0,0 +1,9 @@ + +import Foundation +import WalletConnectNetworking + +public protocol Paringable: AnyObject { + var protocolMethod: ProtocolMethod { get set } + var pairingRequestSubscriber: PairingRequestSubscriber! {get set} + var pairingRequester: PairingRequester! {get set} +} diff --git a/Sources/WalletConnectPairing/Services/PairingPingService.swift b/Sources/WalletConnectPairing/Services/Common/Ping/PairingPingService.swift similarity index 100% rename from Sources/WalletConnectPairing/Services/PairingPingService.swift rename to Sources/WalletConnectPairing/Services/Common/Ping/PairingPingService.swift diff --git a/Sources/WalletConnectPairing/Services/PingRequester.swift b/Sources/WalletConnectPairing/Services/Common/Ping/PingRequester.swift similarity index 100% rename from Sources/WalletConnectPairing/Services/PingRequester.swift rename to Sources/WalletConnectPairing/Services/Common/Ping/PingRequester.swift diff --git a/Sources/WalletConnectPairing/Services/PingResponder.swift b/Sources/WalletConnectPairing/Services/Common/Ping/PingResponder.swift similarity index 100% rename from Sources/WalletConnectPairing/Services/PingResponder.swift rename to Sources/WalletConnectPairing/Services/Common/Ping/PingResponder.swift diff --git a/Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift b/Sources/WalletConnectPairing/Services/Common/Ping/PingResponseSubscriber.swift similarity index 100% rename from Sources/WalletConnectPairing/Services/PingResponseSubscriber.swift rename to Sources/WalletConnectPairing/Services/Common/Ping/PingResponseSubscriber.swift diff --git a/Sources/WalletConnectPairing/PairingProtocolMethod.swift b/Sources/WalletConnectPairing/Types/PairingProtocolMethod.swift similarity index 100% rename from Sources/WalletConnectPairing/PairingProtocolMethod.swift rename to Sources/WalletConnectPairing/Types/PairingProtocolMethod.swift From f50a8162bd5c873718ed321f510ff807d54735de Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 20 Sep 2022 10:17:39 +0200 Subject: [PATCH 20/40] update pairingable protocol --- .../NetworkInteracting.swift | 4 ++ .../NetworkInteractor.swift | 4 ++ .../WalletConnectPairing/PairingClient.swift | 69 +++++++++++++++++-- .../PairingRequester.swift | 2 +- .../Push/PushClient.swift | 2 +- .../Paringable/PairingRequestSubscriber.swift | 33 --------- .../Common/Paringable/Pairingable.swift | 10 +++ .../Common/Paringable/Paringable.swift | 9 --- 8 files changed, 83 insertions(+), 50 deletions(-) rename Sources/WalletConnectPairing/{Services/Common/Paringable => Push}/PairingRequester.swift (96%) delete mode 100644 Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequestSubscriber.swift create mode 100644 Sources/WalletConnectPairing/Services/Common/Paringable/Pairingable.swift delete mode 100644 Sources/WalletConnectPairing/Services/Common/Paringable/Paringable.swift diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 85ca9177e..9e033cc3c 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -18,6 +18,10 @@ public protocol NetworkInteracting { on request: ProtocolMethod ) -> AnyPublisher, Never> + func requestSubscription( + on requests: [ProtocolMethod] + ) -> AnyPublisher, Never> + func responseSubscription( on request: ProtocolMethod ) -> AnyPublisher, Never> diff --git a/Sources/WalletConnectNetworking/NetworkInteractor.swift b/Sources/WalletConnectNetworking/NetworkInteractor.swift index 111d0003f..42b3e71d9 100644 --- a/Sources/WalletConnectNetworking/NetworkInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkInteractor.swift @@ -68,6 +68,10 @@ public class NetworkingInteractor: NetworkInteracting { .eraseToAnyPublisher() } + public func requestSubscription(on requests: [ProtocolMethod]) -> AnyPublisher, Never> where Request : Decodable, Request : Encodable { + <#code#> + } + public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { rpcRequest in diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 2d3f21e6c..3bc9c5420 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -38,15 +38,72 @@ public class PairingClient { return try await appPairService.create() } - public func configureProtocols(with paringables: [Paringable]) { - paringables.forEach { - $0.pairingRequestSubscriber = PairingRequestSubscriber(networkingInteractor: walletPairService.networkingInteractor, logger: logger, kms: walletPairService.kms, protocolMethod: $0.protocolMethod) + public func configureProtocols(with paringables: [Pairingable]) { - $0.pairingRequester = PairingRequester(networkingInteractor: walletPairService.networkingInteractor, kms: walletPairService.kms, logger: logger, protocolMethod: $0.protocolMethod) - } + } +} + +import Foundation +import Combine +import JSONRPC +import WalletConnectUtils +import WalletConnectKMS +import WalletConnectNetworking + + +public class PairingRequestsSubscriber { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private var publishers = [AnyCancellable]() + var onRequest: ((RequestSubscriptionPayload) -> Void)? + let protocolMethod: ProtocolMethod + var pairingables = [Pairingable]() + init(networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + kms: KeyManagementServiceProtocol, + protocolMethod: ProtocolMethod) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.protocolMethod = protocolMethod } - + func setPairingables(_ pairingables: [Pairingable]) { + self.pairingables = pairingables + let methods = pairingables.map{$0.protocolMethod} + subscribeForRequests(methods: methods) + + } + + private func subscribeForRequests(methods: [ProtocolMethod]) { + // TODO - spec tag + let tag = 123456 + networkingInteractor.requestSubscription(on: methods) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + guard let pairingable = pairingables + .first(where: { p in + p.protocolMethod.method == payload.request.method + }) else { + Task { try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: tag, reason: PairError.methodUnsupported) } + return + } + pairingable.requestPublisherSubject.send((topic: payload.topic, request: payload.request)) + + }.store(in: &publishers) + } + +} +public enum PairError: Codable, Equatable, Error, Reason { + case methodUnsupported + + public var code: Int { + //TODO - spec code + return 44444 + } + + //TODO - spec message + public var message: String { + return "Method Unsupported" + } } diff --git a/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequester.swift b/Sources/WalletConnectPairing/Push/PairingRequester.swift similarity index 96% rename from Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequester.swift rename to Sources/WalletConnectPairing/Push/PairingRequester.swift index c92a64d73..8c273fb9c 100644 --- a/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequester.swift +++ b/Sources/WalletConnectPairing/Push/PairingRequester.swift @@ -6,7 +6,7 @@ import WalletConnectKMS import WalletConnectNetworking -public class PairingRequester { +public class PushRequester { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index 4f5da3438..a5fb74f06 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -4,7 +4,7 @@ import WalletConnectUtils import WalletConnectNetworking import Combine -public class PushClient: Paringable { +public class PushClient: Pairingable { public var protocolMethod: ProtocolMethod public var proposalPublisher: AnyPublisher { diff --git a/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequestSubscriber.swift b/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequestSubscriber.swift deleted file mode 100644 index ad5eac2f7..000000000 --- a/Sources/WalletConnectPairing/Services/Common/Paringable/PairingRequestSubscriber.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation -import Combine -import JSONRPC -import WalletConnectUtils -import WalletConnectKMS -import WalletConnectNetworking - - -public class PairingRequestSubscriber { - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private var publishers = [AnyCancellable]() - var onRequest: ((RequestSubscriptionPayload) -> Void)? - let protocolMethod: ProtocolMethod - - init(networkingInteractor: NetworkInteracting, - logger: ConsoleLogging, - kms: KeyManagementServiceProtocol, - protocolMethod: ProtocolMethod) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.protocolMethod = protocolMethod - subscribeForRequest() - } - - func subscribeForRequest() { - - networkingInteractor.requestSubscription(on: protocolMethod) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - onRequest?(payload) - }.store(in: &publishers) - } -} diff --git a/Sources/WalletConnectPairing/Services/Common/Paringable/Pairingable.swift b/Sources/WalletConnectPairing/Services/Common/Paringable/Pairingable.swift new file mode 100644 index 000000000..858cf90e3 --- /dev/null +++ b/Sources/WalletConnectPairing/Services/Common/Paringable/Pairingable.swift @@ -0,0 +1,10 @@ + +import Foundation +import Combine +import WalletConnectNetworking +import JSONRPC + +public protocol Pairingable: AnyObject { + var protocolMethod: ProtocolMethod { get set } + var requestPublisherSubject: PassthroughSubject<(topic: String, request: RPCRequest), Never> {get} +} diff --git a/Sources/WalletConnectPairing/Services/Common/Paringable/Paringable.swift b/Sources/WalletConnectPairing/Services/Common/Paringable/Paringable.swift deleted file mode 100644 index c1580e37d..000000000 --- a/Sources/WalletConnectPairing/Services/Common/Paringable/Paringable.swift +++ /dev/null @@ -1,9 +0,0 @@ - -import Foundation -import WalletConnectNetworking - -public protocol Paringable: AnyObject { - var protocolMethod: ProtocolMethod { get set } - var pairingRequestSubscriber: PairingRequestSubscriber! {get set} - var pairingRequester: PairingRequester! {get set} -} From 70b7cda6523aae3cd5e03eb9119ec768f3e1124d Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 20 Sep 2022 10:40:55 +0200 Subject: [PATCH 21/40] add requestSubscription on requests to networking --- .../NetworkInteracting.swift | 4 +--- .../NetworkInteractor.swift | 14 ++++++++++---- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 9e033cc3c..e0bc1cb3e 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -18,9 +18,7 @@ public protocol NetworkInteracting { on request: ProtocolMethod ) -> AnyPublisher, Never> - func requestSubscription( - on requests: [ProtocolMethod] - ) -> AnyPublisher, Never> + func requestSubscription(on requests: [ProtocolMethod]) -> AnyPublisher<(topic: String, request: RPCRequest), Never> func responseSubscription( on request: ProtocolMethod diff --git a/Sources/WalletConnectNetworking/NetworkInteractor.swift b/Sources/WalletConnectNetworking/NetworkInteractor.swift index 42b3e71d9..b7151eeb2 100644 --- a/Sources/WalletConnectNetworking/NetworkInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkInteractor.swift @@ -56,20 +56,26 @@ public class NetworkingInteractor: NetworkInteracting { } } - public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { + public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return requestPublisher .filter { rpcRequest in return rpcRequest.request.method == request.method } .compactMap { topic, rpcRequest in - guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(Request.self) else { return nil } + guard let id = rpcRequest.id, let request = try? rpcRequest.params?.get(RequestParams.self) else { return nil } return RequestSubscriptionPayload(id: id, topic: topic, request: request) } .eraseToAnyPublisher() } - public func requestSubscription(on requests: [ProtocolMethod]) -> AnyPublisher, Never> where Request : Decodable, Request : Encodable { - <#code#> + public func requestSubscription(on requests: [ProtocolMethod]) -> AnyPublisher<(topic: String, request: RPCRequest), Never> { + return requestPublisher + .filter { rpcRequest in + return requests.contains { protocolMethod in + protocolMethod.method == rpcRequest.request.method + } + } + .eraseToAnyPublisher() } public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { From b8ca28b6509d2c7f4b5f3170fd0346466091eea8 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 20 Sep 2022 11:07:00 +0200 Subject: [PATCH 22/40] savepoint --- .../WalletConnectPairing/PairingClient.swift | 69 ++----------------- .../PairingClientFactory.swift | 4 +- .../Common/Paringable => }/Pairingable.swift | 0 .../Push/PairingRequester.swift | 2 +- .../Push/PushClient.swift | 31 +++------ .../Push/PushClientFactory.swift | 8 +-- .../Common/PairingRequestsSubscriber.swift | 48 +++++++++++++ .../Types/PairError.swift | 16 +++++ 8 files changed, 87 insertions(+), 91 deletions(-) rename Sources/WalletConnectPairing/{Services/Common/Paringable => }/Pairingable.swift (100%) create mode 100644 Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift create mode 100644 Sources/WalletConnectPairing/Types/PairError.swift diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 3bc9c5420..6fbcecd13 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -10,11 +10,13 @@ public class PairingClient { public let socketConnectionStatusPublisher: AnyPublisher let logger: ConsoleLogging private let networkingInteractor: NetworkInteracting + private let pairingRequestsSubscriber: PairingRequestsSubscriber init(appPairService: AppPairService, networkingInteractor: NetworkInteracting, logger: ConsoleLogging, walletPairService: WalletPairService, + pairingRequestsSubscriber: PairingRequestsSubscriber, socketConnectionStatusPublisher: AnyPublisher ) { self.appPairService = appPairService @@ -22,6 +24,7 @@ public class PairingClient { self.networkingInteractor = networkingInteractor self.socketConnectionStatusPublisher = socketConnectionStatusPublisher self.logger = logger + self.pairingRequestsSubscriber = pairingRequestsSubscriber } /// For wallet to establish a pairing and receive an authentication request /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future authentication request. @@ -39,71 +42,7 @@ public class PairingClient { } public func configureProtocols(with paringables: [Pairingable]) { - - } -} - -import Foundation -import Combine -import JSONRPC -import WalletConnectUtils -import WalletConnectKMS -import WalletConnectNetworking - - -public class PairingRequestsSubscriber { - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private var publishers = [AnyCancellable]() - var onRequest: ((RequestSubscriptionPayload) -> Void)? - let protocolMethod: ProtocolMethod - var pairingables = [Pairingable]() - - init(networkingInteractor: NetworkInteracting, - logger: ConsoleLogging, - kms: KeyManagementServiceProtocol, - protocolMethod: ProtocolMethod) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.protocolMethod = protocolMethod - } - - func setPairingables(_ pairingables: [Pairingable]) { - self.pairingables = pairingables - let methods = pairingables.map{$0.protocolMethod} - subscribeForRequests(methods: methods) - - } - - private func subscribeForRequests(methods: [ProtocolMethod]) { - // TODO - spec tag - let tag = 123456 - networkingInteractor.requestSubscription(on: methods) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - guard let pairingable = pairingables - .first(where: { p in - p.protocolMethod.method == payload.request.method - }) else { - Task { try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, tag: tag, reason: PairError.methodUnsupported) } - return - } - pairingable.requestPublisherSubject.send((topic: payload.topic, request: payload.request)) - - }.store(in: &publishers) + pairingRequestsSubscriber.setPairingables(paringables) } - } -public enum PairError: Codable, Equatable, Error, Reason { - case methodUnsupported - - public var code: Int { - //TODO - spec code - return 44444 - } - //TODO - spec message - public var message: String { - return "Method Unsupported" - } - -} diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift index efccb6579..1b1647ca7 100644 --- a/Sources/WalletConnectPairing/PairingClientFactory.swift +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -21,7 +21,9 @@ public struct PairingClientFactory { let walletPaS = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) - return PairingClient(appPairService: appPairService, networkingInteractor: networkingInteractor, logger: logger, walletPairService: walletPaS, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher) + let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingInteractor, logger: logger, kms: kms) + + return PairingClient(appPairService: appPairService, networkingInteractor: networkingInteractor, logger: logger, walletPairService: walletPaS, pairingRequestsSubscriber: pairingRequestsSubscriber, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher) } } diff --git a/Sources/WalletConnectPairing/Services/Common/Paringable/Pairingable.swift b/Sources/WalletConnectPairing/Pairingable.swift similarity index 100% rename from Sources/WalletConnectPairing/Services/Common/Paringable/Pairingable.swift rename to Sources/WalletConnectPairing/Pairingable.swift diff --git a/Sources/WalletConnectPairing/Push/PairingRequester.swift b/Sources/WalletConnectPairing/Push/PairingRequester.swift index 8c273fb9c..ba584d443 100644 --- a/Sources/WalletConnectPairing/Push/PairingRequester.swift +++ b/Sources/WalletConnectPairing/Push/PairingRequester.swift @@ -6,7 +6,7 @@ import WalletConnectKMS import WalletConnectNetworking -public class PushRequester { +public class PushProposer { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index a5fb74f06..80b2645d6 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -2,42 +2,33 @@ import Foundation import WalletConnectKMS import WalletConnectUtils import WalletConnectNetworking +import JSONRPC import Combine public class PushClient: Pairingable { + public var requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest), Never>() public var protocolMethod: ProtocolMethod - public var proposalPublisher: AnyPublisher { - proposalPublisherSubject.eraseToAnyPublisher() - } - private let proposalPublisherSubject = PassthroughSubject() - public var pairingRequestSubscriber: PairingRequestSubscriber! { - didSet { - handleProposal() - } + public var proposalPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { + requestPublisherSubject.eraseToAnyPublisher() } - public var pairingRequester: PairingRequester! + private let pushProposer: PushProposer public let logger: ConsoleLogging init(networkingInteractor: NetworkInteracting, logger: ConsoleLogging, - kms: KeyManagementServiceProtocol) { + kms: KeyManagementServiceProtocol, + protocolMethod: ProtocolMethod, + pushProposer: PushProposer) { self.logger = logger - - protocolMethod = PushProtocolMethod.propose - } - - func handleProposal() { - pairingRequestSubscriber.onRequest = { [unowned self] _ in - logger.debug("Push: received proposal") - proposalPublisherSubject.send("done") - } + self.protocolMethod = protocolMethod + self.pushProposer = pushProposer } public func propose(topic: String) async throws { - try await pairingRequester.request(topic: topic, params: AnyCodable(PushRequestParams())) + try await pushProposer.request(topic: topic, params: AnyCodable(PushRequestParams())) } } diff --git a/Sources/WalletConnectPairing/Push/PushClientFactory.swift b/Sources/WalletConnectPairing/Push/PushClientFactory.swift index 0348185ba..63d420e24 100644 --- a/Sources/WalletConnectPairing/Push/PushClientFactory.swift +++ b/Sources/WalletConnectPairing/Push/PushClientFactory.swift @@ -13,10 +13,10 @@ public struct PushClientFactory { let history = RPCHistory(keyValueStore: historyStorage) - let networkingInt = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) + let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) - - - return PushClient(networkingInteractor: networkingInt, logger: logger, kms: kms) + let protocolMethod = PushProtocolMethod.propose + let pushProposer = PushProposer(networkingInteractor: networkingInteractor, kms: kms, logger: logger, protocolMethod: protocolMethod) + return PushClient(networkingInteractor: networkingInteractor, logger: logger, kms: kms, protocolMethod: protocolMethod, pushProposer: pushProposer) } } diff --git a/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift new file mode 100644 index 000000000..05947f8d7 --- /dev/null +++ b/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift @@ -0,0 +1,48 @@ + +import Foundation +import Combine +import JSONRPC +import WalletConnectUtils +import WalletConnectKMS +import WalletConnectNetworking + + +public class PairingRequestsSubscriber { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private var publishers = [AnyCancellable]() + var onRequest: ((RequestSubscriptionPayload) -> Void)? + var pairingables = [Pairingable]() + + init(networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + kms: KeyManagementServiceProtocol) { + self.networkingInteractor = networkingInteractor + self.kms = kms + } + + func setPairingables(_ pairingables: [Pairingable]) { + self.pairingables = pairingables + let methods = pairingables.map{$0.protocolMethod} + subscribeForRequests(methods: methods) + + } + + private func subscribeForRequests(methods: [ProtocolMethod]) { + // TODO - spec tag + let tag = 123456 + networkingInteractor.requestSubscription(on: methods) + .sink { [unowned self] topic, request in + guard let pairingable = pairingables + .first(where: { p in + p.protocolMethod.method == request.method + }) else { + Task { try await networkingInteractor.respondError(topic: topic, requestId: request.id!, tag: tag, reason: PairError.methodUnsupported) } + return + } + pairingable.requestPublisherSubject.send((topic: topic, request: request)) + + }.store(in: &publishers) + } + +} diff --git a/Sources/WalletConnectPairing/Types/PairError.swift b/Sources/WalletConnectPairing/Types/PairError.swift new file mode 100644 index 000000000..99e93f336 --- /dev/null +++ b/Sources/WalletConnectPairing/Types/PairError.swift @@ -0,0 +1,16 @@ +import WalletConnectNetworking + +public enum PairError: Codable, Equatable, Error, Reason { + case methodUnsupported + + public var code: Int { + //TODO - spec code + return 44444 + } + + //TODO - spec message + public var message: String { + return "Method Unsupported" + } + +} From 2901a006e1b40f077a7a6fe60b0f28d4df5a655a Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 20 Sep 2022 13:03:35 +0200 Subject: [PATCH 23/40] savepoint --- .../Pairing/PairingTests.swift | 20 +++++++++---------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 3887c7ca8..578e6a7cd 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -15,8 +15,8 @@ final class PairingTests: XCTestCase { private var publishers = [AnyCancellable]() override func setUp() { - appPairingClient = makeClient(prefix: "👻 App") - walletPairingClient = makeClient(prefix: "🤑 Wallet") + appPairingClient = makeClient(prefix: "👻 App", keychain: appKeychain) + walletPairingClient = makeClient(prefix: "🤑 Wallet", keychain: walletKeychain) let expectation = expectation(description: "Wait Clients Connected") expectation.expectedFulfillmentCount = 2 @@ -36,21 +36,21 @@ final class PairingTests: XCTestCase { wait(for: [expectation], timeout: 5) } - - func makeClient(prefix: String) -> PairingClient { + func makeClient(prefix: String, keychain: KeychainStorageMock) -> PairingClient { let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) let projectId = "3ca2919724fbfa5456a25194e369a8b4" - let keychain = KeychainStorageMock() let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) let pairingClient = PairingClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) return pairingClient } - func makePushClient(suffix: String) -> PushClient { + let appKeychain = KeychainStorageMock() + let walletKeychain = KeychainStorageMock() + + func makePushClient(suffix: String, keychain: KeychainStorageMock) -> PushClient { let logger = ConsoleLogger(suffix: suffix, loggingLevel: .debug) let projectId = "3ca2919724fbfa5456a25194e369a8b4" - let keychain = KeychainStorageMock() let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) return PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) } @@ -58,8 +58,8 @@ final class PairingTests: XCTestCase { func testProposePushOnPairing() async { let exp = expectation(description: "") - let appPushClient = makePushClient(suffix: "👻 App") - let walletPushClient = makePushClient(suffix: "🤑 Wallet") + let appPushClient = makePushClient(suffix: "👻 App", keychain: appKeychain) + let walletPushClient = makePushClient(suffix: "🤑 Wallet", keychain: walletKeychain) walletPushClient.proposalPublisher.sink { _ in exp.fulfill() @@ -75,8 +75,6 @@ final class PairingTests: XCTestCase { try! await appPushClient.propose(topic: uri.topic) - - wait(for: [exp], timeout: 2) } From 1bc9ce3f47697d5031044a0bf3db5d77b2e68a3f Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 20 Sep 2022 15:17:17 +0200 Subject: [PATCH 24/40] savepoint --- Example/IntegrationTests/Pairing/PairingTests.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 578e6a7cd..229323460 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -55,6 +55,13 @@ final class PairingTests: XCTestCase { return PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) } + func makeAppClients() -> (PairingClient, PushClient) { + + } + + func makeWalletClient() -> (PairingClient, PushClient) { + } + func testProposePushOnPairing() async { let exp = expectation(description: "") From 1e1646e72a637622b845ff0963fde301bf14c772 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 21 Sep 2022 07:54:01 +0200 Subject: [PATCH 25/40] remove socket connection observing from integration tests --- Example/IntegrationTests/Auth/AuthTests.swift | 17 --------------- Example/IntegrationTests/Chat/ChatTests.swift | 21 ------------------- .../Pairing/PairingTests.swift | 19 +---------------- 3 files changed, 1 insertion(+), 56 deletions(-) diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 6abe13983..4ee2bf33d 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -16,23 +16,6 @@ final class AuthTests: XCTestCase { app = makeClient(prefix: "👻 App") let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")! wallet = makeClient(prefix: "🤑 Wallet", account: walletAccount) - - let expectation = expectation(description: "Wait Clients Connected") - expectation.expectedFulfillmentCount = 2 - - app.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - wallet.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - wait(for: [expectation], timeout: 5) } func makeClient(prefix: String, account: Account? = nil) -> AuthClient { diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index 191ea398f..c4863b8af 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -16,27 +16,6 @@ final class ChatTests: XCTestCase { registry = KeyValueRegistry() invitee = makeClient(prefix: "🦖 Registered") inviter = makeClient(prefix: "🍄 Inviter") - - waitClientsConnected() - } - - private func waitClientsConnected() { - let expectation = expectation(description: "Wait Clients Connected") - expectation.expectedFulfillmentCount = 2 - - invitee.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - inviter.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - wait(for: [expectation], timeout: 5) } func makeClient(prefix: String) -> ChatClient { diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index e77efa3e4..4b320a1c2 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -20,23 +20,6 @@ final class PairingTests: XCTestCase { override func setUp() { (appPairingClient, appPushClient) = makeClients(prefix: "👻 App") (walletPairingClient, walletPushClient) = makeClients(prefix: "🤑 Wallet") - - let expectation = expectation(description: "Wait Clients Connected") - expectation.expectedFulfillmentCount = 2 - - appPairingClient.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - walletPairingClient.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - wait(for: [expectation], timeout: 5) } func makeClients(prefix: String) -> (PairingClient, PushClient) { @@ -54,7 +37,7 @@ final class PairingTests: XCTestCase { func testProposePushOnPairing() async { let exp = expectation(description: "") - + walletPushClient.proposalPublisher.sink { _ in exp.fulfill() }.store(in: &publishers) From 67e3b156e08a03976f1184c6edb010f46ceb9fe3 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 21 Sep 2022 08:25:32 +0200 Subject: [PATCH 26/40] restore on connection publisher --- Example/IntegrationTests/Auth/AuthTests.swift | 17 +++++++++++++++ Example/IntegrationTests/Chat/ChatTests.swift | 21 +++++++++++++++++++ .../NetworkInteracting.swift | 3 +-- .../NetworkInteractor.swift | 12 +---------- .../Common/PairingRequestsSubscriber.swift | 2 +- 5 files changed, 41 insertions(+), 14 deletions(-) diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 4ee2bf33d..6abe13983 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -16,6 +16,23 @@ final class AuthTests: XCTestCase { app = makeClient(prefix: "👻 App") let walletAccount = Account(chainIdentifier: "eip155:1", address: "0x724d0D2DaD3fbB0C168f947B87Fa5DBe36F1A8bf")! wallet = makeClient(prefix: "🤑 Wallet", account: walletAccount) + + let expectation = expectation(description: "Wait Clients Connected") + expectation.expectedFulfillmentCount = 2 + + app.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + wallet.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + wait(for: [expectation], timeout: 5) } func makeClient(prefix: String, account: Account? = nil) -> AuthClient { diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index c4863b8af..191ea398f 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -16,6 +16,27 @@ final class ChatTests: XCTestCase { registry = KeyValueRegistry() invitee = makeClient(prefix: "🦖 Registered") inviter = makeClient(prefix: "🍄 Inviter") + + waitClientsConnected() + } + + private func waitClientsConnected() { + let expectation = expectation(description: "Wait Clients Connected") + expectation.expectedFulfillmentCount = 2 + + invitee.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + inviter.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + wait(for: [expectation], timeout: 5) } func makeClient(prefix: String) -> ChatClient { diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index e0bc1cb3e..56b81caae 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -6,6 +6,7 @@ import WalletConnectRelay public protocol NetworkInteracting { var socketConnectionStatusPublisher: AnyPublisher { get } + var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { get } func subscribe(topic: String) async throws func unsubscribe(topic: String) func request(_ request: RPCRequest, topic: String, tag: Int, envelopeType: Envelope.EnvelopeType) async throws @@ -18,8 +19,6 @@ public protocol NetworkInteracting { on request: ProtocolMethod ) -> AnyPublisher, Never> - func requestSubscription(on requests: [ProtocolMethod]) -> AnyPublisher<(topic: String, request: RPCRequest), Never> - func responseSubscription( on request: ProtocolMethod ) -> AnyPublisher, Never> diff --git a/Sources/WalletConnectNetworking/NetworkInteractor.swift b/Sources/WalletConnectNetworking/NetworkInteractor.swift index b7151eeb2..c6a265b50 100644 --- a/Sources/WalletConnectNetworking/NetworkInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkInteractor.swift @@ -15,7 +15,7 @@ public class NetworkingInteractor: NetworkInteracting { private let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest), Never>() private let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse), Never>() - private var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { requestPublisherSubject.eraseToAnyPublisher() } @@ -68,16 +68,6 @@ public class NetworkingInteractor: NetworkInteracting { .eraseToAnyPublisher() } - public func requestSubscription(on requests: [ProtocolMethod]) -> AnyPublisher<(topic: String, request: RPCRequest), Never> { - return requestPublisher - .filter { rpcRequest in - return requests.contains { protocolMethod in - protocolMethod.method == rpcRequest.request.method - } - } - .eraseToAnyPublisher() - } - public func responseSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return responsePublisher .filter { rpcRequest in diff --git a/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift index 05947f8d7..6295ed66e 100644 --- a/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift +++ b/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift @@ -31,7 +31,7 @@ public class PairingRequestsSubscriber { private func subscribeForRequests(methods: [ProtocolMethod]) { // TODO - spec tag let tag = 123456 - networkingInteractor.requestSubscription(on: methods) + networkingInteractor.requestPublisher .sink { [unowned self] topic, request in guard let pairingable = pairingables .first(where: { p in From 1395b231938511f79d0f0f8b9bba83ee2eceec85 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 21 Sep 2022 08:42:30 +0200 Subject: [PATCH 27/40] remove duplicated file --- .../IntegrationTests/Auth/PairingTest.swift | 83 ------------------- .../Pairing/PairingTests.swift | 17 ++++ 2 files changed, 17 insertions(+), 83 deletions(-) delete mode 100644 Example/IntegrationTests/Auth/PairingTest.swift diff --git a/Example/IntegrationTests/Auth/PairingTest.swift b/Example/IntegrationTests/Auth/PairingTest.swift deleted file mode 100644 index 96a092b41..000000000 --- a/Example/IntegrationTests/Auth/PairingTest.swift +++ /dev/null @@ -1,83 +0,0 @@ -import Foundation -import XCTest -import WalletConnectUtils -@testable import WalletConnectKMS -import WalletConnectRelay -import Combine -import WalletConnectNetworking -import WalletConnectPairing - - -final class Pairingtests: XCTestCase { - var appPairingClient: PairingClient! - var walletPairingClient: PairingClient! - - private var publishers = [AnyCancellable]() - - override func setUp() { - appPairingClient = makeClient(prefix: "👻 App") - walletPairingClient = makeClient(prefix: "🤑 Wallet") - - let expectation = expectation(description: "Wait Clients Connected") - expectation.expectedFulfillmentCount = 2 - - appPairingClient.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - walletPairingClient.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - wait(for: [expectation], timeout: 5) - } - - - func makeClient(prefix: String) -> PairingClient { - let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) - let projectId = "3ca2919724fbfa5456a25194e369a8b4" - let keychain = KeychainStorageMock() - let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) - - let pairingClient = PairingClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) - return pairingClient - } - - func makePushClient() -> PushClient { - let logger = ConsoleLogger(suffix: "", loggingLevel: .debug) - let projectId = "3ca2919724fbfa5456a25194e369a8b4" - let keychain = KeychainStorageMock() - let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) - return PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) - } - - func testProposePushOnPairing() async { - let exp = expectation(description: "") - let appPushClient = makePushClient() - let walletPushClient = makePushClient() - - appPairingClient.configure(with: [appPushClient]) - - walletPairingClient.configure(with: [walletPushClient, ]) - - let uri = try! await appPairingClient.create() - - try! await walletPairingClient.pair(uri: uri) - - try! await appPushClient.propose(topic: uri.topic) - - walletPushClient.proposalPublisher.sink { _ in - exp.fulfill() - print("received push proposal") - }.store(in: &publishers) - - wait(for: [exp], timeout: 2) - - } - -} - diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 4b320a1c2..7594348ef 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -20,6 +20,23 @@ final class PairingTests: XCTestCase { override func setUp() { (appPairingClient, appPushClient) = makeClients(prefix: "👻 App") (walletPairingClient, walletPushClient) = makeClients(prefix: "🤑 Wallet") + + let expectation = expectation(description: "Wait Clients Connected") + expectation.expectedFulfillmentCount = 2 + + appPairingClient.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + walletPairingClient.socketConnectionStatusPublisher.sink { status in + if status == .connected { + expectation.fulfill() + } + }.store(in: &publishers) + + wait(for: [expectation], timeout: 5) } func makeClients(prefix: String) -> (PairingClient, PushClient) { From 227ac545e32c7517fc20b05415b667286c1349f1 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 21 Sep 2022 11:29:38 +0200 Subject: [PATCH 28/40] self review changes --- Sources/WalletConnectPairing/PairingClient.swift | 4 ++-- Sources/WalletConnectPairing/PairingClientFactory.swift | 7 ++----- Sources/WalletConnectPairing/Push/PushClientFactory.swift | 4 +--- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 6fbcecd13..eb7797381 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -26,8 +26,8 @@ public class PairingClient { self.logger = logger self.pairingRequestsSubscriber = pairingRequestsSubscriber } - /// For wallet to establish a pairing and receive an authentication request - /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future authentication request. + /// For wallet to establish a pairing + /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future requests. /// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp or delivered with universal linking. /// /// Throws Error: diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift index 34ca7077c..a957e2d0f 100644 --- a/Sources/WalletConnectPairing/PairingClientFactory.swift +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -8,13 +8,10 @@ public struct PairingClientFactory { public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PairingClient { let kms = KeyManagementService(keychain: keychainStorage) let serializer = Serializer(kms: kms) - let kv = RuntimeKeyValueStorage() - let history = RPCHistoryFactory.createForNetwork(keyValueStorage: kv) - + let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) - let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: kv, identifier: ""))) - + let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: ""))) let appPairService = AppPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) diff --git a/Sources/WalletConnectPairing/Push/PushClientFactory.swift b/Sources/WalletConnectPairing/Push/PushClientFactory.swift index 6e0574950..89c11ea80 100644 --- a/Sources/WalletConnectPairing/Push/PushClientFactory.swift +++ b/Sources/WalletConnectPairing/Push/PushClientFactory.swift @@ -8,9 +8,7 @@ public struct PushClientFactory { public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PushClient { let kms = KeyManagementService(keychain: keychainStorage) let serializer = Serializer(kms: kms) - let kv = RuntimeKeyValueStorage() - let history = RPCHistoryFactory.createForNetwork(keyValueStorage: kv) - + let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) From 6aab69c551c775da4952a55e70f7c1a0d8c5f3be Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 22 Sep 2022 16:09:09 +0200 Subject: [PATCH 29/40] savepoint --- Sources/WalletConnectPairing/PairingClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index eb7797381..4b108addf 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -8,7 +8,7 @@ public class PairingClient { private let walletPairService: WalletPairService private let appPairService: AppPairService public let socketConnectionStatusPublisher: AnyPublisher - let logger: ConsoleLogging + private let logger: ConsoleLogging private let networkingInteractor: NetworkInteracting private let pairingRequestsSubscriber: PairingRequestsSubscriber From be56aab76e7cf3626ff0a60af1aae1d853f2bd08 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 22 Sep 2022 18:05:32 +0300 Subject: [PATCH 30/40] Pairingable removed --- .../Pairing/PairingTests.swift | 8 +--- .../WalletConnectPairing/PairingClient.swift | 7 --- .../PairingClientFactory.swift | 4 +- .../WalletConnectPairing/Pairingable.swift | 10 ---- .../Push/PairingRequester.swift | 8 +--- .../Push/PushClient.swift | 35 +++++++++----- .../Push/PushClientFactory.swift | 5 +- .../Common/PairingRequestsSubscriber.swift | 48 ------------------- 8 files changed, 30 insertions(+), 95 deletions(-) delete mode 100644 Sources/WalletConnectPairing/Pairingable.swift delete mode 100644 Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 7594348ef..304d9dc76 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -5,7 +5,7 @@ import WalletConnectUtils import WalletConnectRelay import Combine import WalletConnectNetworking -import WalletConnectPairing +@testable import WalletConnectPairing final class PairingTests: XCTestCase { @@ -59,10 +59,6 @@ final class PairingTests: XCTestCase { exp.fulfill() }.store(in: &publishers) - appPairingClient.configureProtocols(with: [appPushClient]) - - walletPairingClient.configureProtocols(with: [walletPushClient]) - let uri = try! await appPairingClient.create() try! await walletPairingClient.pair(uri: uri) @@ -70,8 +66,6 @@ final class PairingTests: XCTestCase { try! await appPushClient.propose(topic: uri.topic) wait(for: [exp], timeout: 2) - } - } diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index 4b108addf..bf64bcab0 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -10,13 +10,11 @@ public class PairingClient { public let socketConnectionStatusPublisher: AnyPublisher private let logger: ConsoleLogging private let networkingInteractor: NetworkInteracting - private let pairingRequestsSubscriber: PairingRequestsSubscriber init(appPairService: AppPairService, networkingInteractor: NetworkInteracting, logger: ConsoleLogging, walletPairService: WalletPairService, - pairingRequestsSubscriber: PairingRequestsSubscriber, socketConnectionStatusPublisher: AnyPublisher ) { self.appPairService = appPairService @@ -24,7 +22,6 @@ public class PairingClient { self.networkingInteractor = networkingInteractor self.socketConnectionStatusPublisher = socketConnectionStatusPublisher self.logger = logger - self.pairingRequestsSubscriber = pairingRequestsSubscriber } /// For wallet to establish a pairing /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future requests. @@ -40,9 +37,5 @@ public class PairingClient { public func create() async throws -> WalletConnectURI { return try await appPairService.create() } - - public func configureProtocols(with paringables: [Pairingable]) { - pairingRequestsSubscriber.setPairingables(paringables) - } } diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift index a957e2d0f..7f4cc3bfb 100644 --- a/Sources/WalletConnectPairing/PairingClientFactory.swift +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -17,9 +17,7 @@ public struct PairingClientFactory { let walletPaS = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) - let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingInteractor, logger: logger, kms: kms) - - return PairingClient(appPairService: appPairService, networkingInteractor: networkingInteractor, logger: logger, walletPairService: walletPaS, pairingRequestsSubscriber: pairingRequestsSubscriber, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher) + return PairingClient(appPairService: appPairService, networkingInteractor: networkingInteractor, logger: logger, walletPairService: walletPaS, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher) } } diff --git a/Sources/WalletConnectPairing/Pairingable.swift b/Sources/WalletConnectPairing/Pairingable.swift deleted file mode 100644 index 858cf90e3..000000000 --- a/Sources/WalletConnectPairing/Pairingable.swift +++ /dev/null @@ -1,10 +0,0 @@ - -import Foundation -import Combine -import WalletConnectNetworking -import JSONRPC - -public protocol Pairingable: AnyObject { - var protocolMethod: ProtocolMethod { get set } - var requestPublisherSubject: PassthroughSubject<(topic: String, request: RPCRequest), Never> {get} -} diff --git a/Sources/WalletConnectPairing/Push/PairingRequester.swift b/Sources/WalletConnectPairing/Push/PairingRequester.swift index ba584d443..bdc89d2dc 100644 --- a/Sources/WalletConnectPairing/Push/PairingRequester.swift +++ b/Sources/WalletConnectPairing/Push/PairingRequester.swift @@ -10,21 +10,17 @@ public class PushProposer { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging - let protocolMethod: ProtocolMethod init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, - logger: ConsoleLogging, - protocolMethod: ProtocolMethod) { + logger: ConsoleLogging) { self.networkingInteractor = networkingInteractor self.kms = kms self.logger = logger - self.protocolMethod = protocolMethod } func request(topic: String, params: AnyCodable) async throws { - let request = RPCRequest(method: protocolMethod.method, params: params) - + let request = RPCRequest(method: PushProtocolMethod.propose.method, params: params) try await networkingInteractor.requestNetworkAck(request, topic: topic, tag: PushProtocolMethod.propose.requestTag) } } diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index 80b2645d6..0a75896ea 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -1,34 +1,47 @@ import Foundation +import JSONRPC +import Combine import WalletConnectKMS import WalletConnectUtils import WalletConnectNetworking -import JSONRPC -import Combine -public class PushClient: Pairingable { - public var requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest), Never>() +public class PushClient { - public var protocolMethod: ProtocolMethod + private var publishers = Set() - public var proposalPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { + let requestPublisherSubject = PassthroughSubject<(topic: String, params: PushRequestParams), Never>() + + var proposalPublisher: AnyPublisher<(topic: String, params: PushRequestParams), Never> { requestPublisherSubject.eraseToAnyPublisher() } - private let pushProposer: PushProposer - public let logger: ConsoleLogging - init(networkingInteractor: NetworkInteracting, + private let pushProposer: PushProposer + private let networkInteractor: NetworkInteracting + + init(networkInteractor: NetworkInteracting, logger: ConsoleLogging, kms: KeyManagementServiceProtocol, - protocolMethod: ProtocolMethod, pushProposer: PushProposer) { + self.networkInteractor = networkInteractor self.logger = logger - self.protocolMethod = protocolMethod self.pushProposer = pushProposer + + setupPairingSubscriptions() } public func propose(topic: String) async throws { try await pushProposer.request(topic: topic, params: AnyCodable(PushRequestParams())) } } + +private extension PushClient { + + func setupPairingSubscriptions() { + networkInteractor.requestSubscription(on: PushProtocolMethod.propose) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + requestPublisherSubject.send((payload.topic, payload.request)) + }.store(in: &publishers) + } +} diff --git a/Sources/WalletConnectPairing/Push/PushClientFactory.swift b/Sources/WalletConnectPairing/Push/PushClientFactory.swift index 89c11ea80..ead91fe59 100644 --- a/Sources/WalletConnectPairing/Push/PushClientFactory.swift +++ b/Sources/WalletConnectPairing/Push/PushClientFactory.swift @@ -12,8 +12,7 @@ public struct PushClientFactory { let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) - let protocolMethod = PushProtocolMethod.propose - let pushProposer = PushProposer(networkingInteractor: networkingInteractor, kms: kms, logger: logger, protocolMethod: protocolMethod) - return PushClient(networkingInteractor: networkingInteractor, logger: logger, kms: kms, protocolMethod: protocolMethod, pushProposer: pushProposer) + let pushProposer = PushProposer(networkingInteractor: networkingInteractor, kms: kms, logger: logger) + return PushClient(networkInteractor: networkingInteractor, logger: logger, kms: kms, pushProposer: pushProposer) } } diff --git a/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift deleted file mode 100644 index 6295ed66e..000000000 --- a/Sources/WalletConnectPairing/Services/Common/PairingRequestsSubscriber.swift +++ /dev/null @@ -1,48 +0,0 @@ - -import Foundation -import Combine -import JSONRPC -import WalletConnectUtils -import WalletConnectKMS -import WalletConnectNetworking - - -public class PairingRequestsSubscriber { - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private var publishers = [AnyCancellable]() - var onRequest: ((RequestSubscriptionPayload) -> Void)? - var pairingables = [Pairingable]() - - init(networkingInteractor: NetworkInteracting, - logger: ConsoleLogging, - kms: KeyManagementServiceProtocol) { - self.networkingInteractor = networkingInteractor - self.kms = kms - } - - func setPairingables(_ pairingables: [Pairingable]) { - self.pairingables = pairingables - let methods = pairingables.map{$0.protocolMethod} - subscribeForRequests(methods: methods) - - } - - private func subscribeForRequests(methods: [ProtocolMethod]) { - // TODO - spec tag - let tag = 123456 - networkingInteractor.requestPublisher - .sink { [unowned self] topic, request in - guard let pairingable = pairingables - .first(where: { p in - p.protocolMethod.method == request.method - }) else { - Task { try await networkingInteractor.respondError(topic: topic, requestId: request.id!, tag: tag, reason: PairError.methodUnsupported) } - return - } - pairingable.requestPublisherSubject.send((topic: topic, request: request)) - - }.store(in: &publishers) - } - -} From 1a3af64263bde67be8e2f080a623e7d724c4d14c Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Thu, 22 Sep 2022 22:42:27 +0300 Subject: [PATCH 31/40] PairingRegisterer --- .../Pairing/PairingTests.swift | 14 ++++---- .../WalletConnectPairing/PairingClient.swift | 9 ++++- .../PairingClientFactory.swift | 25 +++++++++---- .../PairingRegisterer.swift | 6 ++++ .../{Push => }/PairingRequester.swift | 0 .../PairingRequestsSubscriber.swift | 36 +++++++++++++++++++ .../Push/PushClient.swift | 7 +++- .../Push/PushClientFactory.swift | 14 +++++--- 8 files changed, 93 insertions(+), 18 deletions(-) create mode 100644 Sources/WalletConnectPairing/PairingRegisterer.swift rename Sources/WalletConnectPairing/{Push => }/PairingRequester.swift (100%) create mode 100644 Sources/WalletConnectPairing/PairingRequestsSubscriber.swift diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 304d9dc76..bc9aa7338 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -15,6 +15,8 @@ final class PairingTests: XCTestCase { var appPushClient: PushClient! var walletPushClient: PushClient! + var pairingStorage: PairingStorage! + private var publishers = [AnyCancellable]() override func setUp() { @@ -47,23 +49,23 @@ final class PairingTests: XCTestCase { let pairingClient = PairingClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) - let pushClient = PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) + let pushClient = PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient, pairingClient: pairingClient) return (pairingClient, pushClient) } - func testProposePushOnPairing() async { - let exp = expectation(description: "") + func testProposePushOnPairing() async throws { + let exp = expectation(description: "testProposePushOnPairing") walletPushClient.proposalPublisher.sink { _ in exp.fulfill() }.store(in: &publishers) - let uri = try! await appPairingClient.create() + let uri = try await appPairingClient.create() - try! await walletPairingClient.pair(uri: uri) + try await walletPairingClient.pair(uri: uri) - try! await appPushClient.propose(topic: uri.topic) + try await appPushClient.propose(topic: uri.topic) wait(for: [exp], timeout: 2) } diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index bf64bcab0..de16ec03b 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -4,17 +4,19 @@ import WalletConnectRelay import WalletConnectNetworking import Combine -public class PairingClient { +public class PairingClient: PairingRegisterer { private let walletPairService: WalletPairService private let appPairService: AppPairService public let socketConnectionStatusPublisher: AnyPublisher private let logger: ConsoleLogging private let networkingInteractor: NetworkInteracting + private let pairingRequestsSubscriber: PairingRequestsSubscriber init(appPairService: AppPairService, networkingInteractor: NetworkInteracting, logger: ConsoleLogging, walletPairService: WalletPairService, + pairingRequestsSubscriber: PairingRequestsSubscriber, socketConnectionStatusPublisher: AnyPublisher ) { self.appPairService = appPairService @@ -22,6 +24,7 @@ public class PairingClient { self.networkingInteractor = networkingInteractor self.socketConnectionStatusPublisher = socketConnectionStatusPublisher self.logger = logger + self.pairingRequestsSubscriber = pairingRequestsSubscriber } /// For wallet to establish a pairing /// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future requests. @@ -37,5 +40,9 @@ public class PairingClient { public func create() async throws -> WalletConnectURI { return try await appPairService.create() } + + public func register(method: ProtocolMethod) { + pairingRequestsSubscriber.subscribeForRequest(method) + } } diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift index 7f4cc3bfb..63a447038 100644 --- a/Sources/WalletConnectPairing/PairingClientFactory.swift +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -5,19 +5,32 @@ import WalletConnectKMS import WalletConnectNetworking public struct PairingClientFactory { - public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PairingClient { + + public static func create(relayClient: RelayClient) -> PairingClient { + let logger = ConsoleLogger(loggingLevel: .off) + let keyValueStorage = UserDefaults.standard + let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") + return PairingClientFactory.create(logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, relayClient: relayClient) + } + + static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PairingClient { let kms = KeyManagementService(keychain: keychainStorage) let serializer = Serializer(kms: kms) let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) - let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: ""))) - let appPairService = AppPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) + let walletPairService = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) + let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingInteractor, pairingStorage: pairingStore, logger: logger) - let walletPaS = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) - - return PairingClient(appPairService: appPairService, networkingInteractor: networkingInteractor, logger: logger, walletPairService: walletPaS, socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher) + return PairingClient( + appPairService: appPairService, + networkingInteractor: networkingInteractor, + logger: logger, + walletPairService: walletPairService, + pairingRequestsSubscriber: pairingRequestsSubscriber, + socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher + ) } } diff --git a/Sources/WalletConnectPairing/PairingRegisterer.swift b/Sources/WalletConnectPairing/PairingRegisterer.swift new file mode 100644 index 000000000..6eafa7b1e --- /dev/null +++ b/Sources/WalletConnectPairing/PairingRegisterer.swift @@ -0,0 +1,6 @@ +import Foundation +import WalletConnectNetworking + +public protocol PairingRegisterer { + func register(method: ProtocolMethod) +} diff --git a/Sources/WalletConnectPairing/Push/PairingRequester.swift b/Sources/WalletConnectPairing/PairingRequester.swift similarity index 100% rename from Sources/WalletConnectPairing/Push/PairingRequester.swift rename to Sources/WalletConnectPairing/PairingRequester.swift diff --git a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift new file mode 100644 index 000000000..f997b886f --- /dev/null +++ b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift @@ -0,0 +1,36 @@ +import Foundation +import Combine +import WalletConnectUtils +import WalletConnectNetworking + +public class PairingRequestsSubscriber { + private let networkingInteractor: NetworkInteracting + private let pairingStorage: PairingStorage + private var publishers = Set() + + init(networkingInteractor: NetworkInteracting, pairingStorage: PairingStorage, logger: ConsoleLogging) { + self.networkingInteractor = networkingInteractor + self.pairingStorage = pairingStorage + } + + func subscribeForRequest(_ protocolMethod: ProtocolMethod) { + networkingInteractor.requestPublisher + // Pairing requests only + .filter { [unowned self] payload in + return pairingStorage.hasPairing(forTopic: payload.topic) + } + // Wrong method + .filter { payload in + return payload.request.method != protocolMethod.method + } + // Respond error + .sink { [unowned self] topic, request in + Task(priority: .high) { + // TODO - spec tag + try await networkingInteractor.respondError(topic: topic, requestId: request.id!, tag: 123456, reason: PairError.methodUnsupported) + } + + }.store(in: &publishers) + } + +} diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index 0a75896ea..6df4561ed 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -19,14 +19,17 @@ public class PushClient { private let pushProposer: PushProposer private let networkInteractor: NetworkInteracting + private let pairingRegisterer: PairingRegisterer init(networkInteractor: NetworkInteracting, logger: ConsoleLogging, kms: KeyManagementServiceProtocol, - pushProposer: PushProposer) { + pushProposer: PushProposer, + pairingRegisterer: PairingRegisterer) { self.networkInteractor = networkInteractor self.logger = logger self.pushProposer = pushProposer + self.pairingRegisterer = pairingRegisterer setupPairingSubscriptions() } @@ -39,6 +42,8 @@ public class PushClient { private extension PushClient { func setupPairingSubscriptions() { + pairingRegisterer.register(method: PushProtocolMethod.propose) + networkInteractor.requestSubscription(on: PushProtocolMethod.propose) .sink { [unowned self] (payload: RequestSubscriptionPayload) in requestPublisherSubject.send((payload.topic, payload.request)) diff --git a/Sources/WalletConnectPairing/Push/PushClientFactory.swift b/Sources/WalletConnectPairing/Push/PushClientFactory.swift index ead91fe59..21c4cf74c 100644 --- a/Sources/WalletConnectPairing/Push/PushClientFactory.swift +++ b/Sources/WalletConnectPairing/Push/PushClientFactory.swift @@ -5,14 +5,20 @@ import WalletConnectKMS import WalletConnectNetworking public struct PushClientFactory { - public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PushClient { + + static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient, pairingClient: PairingClient) -> PushClient { let kms = KeyManagementService(keychain: keychainStorage) let serializer = Serializer(kms: kms) let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) - let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) - let pushProposer = PushProposer(networkingInteractor: networkingInteractor, kms: kms, logger: logger) - return PushClient(networkInteractor: networkingInteractor, logger: logger, kms: kms, pushProposer: pushProposer) + + return PushClient( + networkInteractor: networkingInteractor, + logger: logger, + kms: kms, + pushProposer: pushProposer, + pairingRegisterer: pairingClient + ) } } From 87006a75645ee13cad8373c9c0113895a2b44f46 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 23 Sep 2022 15:44:52 +0300 Subject: [PATCH 32/40] Push propose error publisher --- Sources/WalletConnectPairing/Push/PushClient.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index 6df4561ed..5b232688b 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -44,6 +44,11 @@ private extension PushClient { func setupPairingSubscriptions() { pairingRegisterer.register(method: PushProtocolMethod.propose) + networkInteractor.responseErrorSubscription(on: PushProtocolMethod.propose) + .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in + print("error") + }.store(in: &publishers) + networkInteractor.requestSubscription(on: PushProtocolMethod.propose) .sink { [unowned self] (payload: RequestSubscriptionPayload) in requestPublisherSubject.send((payload.topic, payload.request)) From eb1ce87585412f61b25ebb69a07e7478c2220b57 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Fri, 23 Sep 2022 15:46:38 +0300 Subject: [PATCH 33/40] Build errors --- Example/IntegrationTests/Pairing/PairingTests.swift | 2 +- Sources/WalletConnectPairing/Push/PushClient.swift | 2 +- Tests/TestingUtils/NetworkingInteractorMock.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index bc9aa7338..5a581a215 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -55,7 +55,7 @@ final class PairingTests: XCTestCase { } func testProposePushOnPairing() async throws { - let exp = expectation(description: "testProposePushOnPairing") + let exp = expectation(description: "testProposePushOnPairing") walletPushClient.proposalPublisher.sink { _ in exp.fulfill() diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index 5b232688b..4739593e5 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -46,7 +46,7 @@ private extension PushClient { networkInteractor.responseErrorSubscription(on: PushProtocolMethod.propose) .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in - print("error") + logger.error(payload.error.localizedDescription) }.store(in: &publishers) networkInteractor.requestSubscription(on: PushProtocolMethod.propose) diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 286390803..0ba467bab 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -30,7 +30,7 @@ public class NetworkingInteractorMock: NetworkInteracting { public let requestPublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest), Never>() public let responsePublisherSubject = PassthroughSubject<(topic: String, request: RPCRequest, response: RPCResponse), Never>() - private var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { + public var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { requestPublisherSubject.eraseToAnyPublisher() } From 34728a239579f12fa1d12016fad91cff834c2928 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 27 Sep 2022 08:59:06 +0600 Subject: [PATCH 34/40] Cache key updated --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bcbcf09cf..7218c2e65 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -39,9 +39,9 @@ jobs: path: | .build SourcePackagesCache - key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-spm1-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm- + ${{ runner.os }}-spm1- - uses: ./.github/actions/ci with: @@ -65,9 +65,9 @@ jobs: path: | .build SourcePackagesCache - key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-spm1-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm- + ${{ runner.os }}-spm1- - uses: ./.github/actions/ci with: From cc80680d1d096fbc19adb4dcd7aa48f36d4bb9dd Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 27 Sep 2022 09:09:44 +0600 Subject: [PATCH 35/40] Resolve Dependencies after cache restore --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7218c2e65..fe608f08c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,12 +28,6 @@ jobs: - name: Setup Xcode Version uses: maxim-lobanov/setup-xcode@v1 - - name: Resolve Dependencies - shell: bash - run: " - xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme DApp -clonedSourcePackagesDirPath SourcePackagesCache; \ - xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme WalletConnect -clonedSourcePackagesDirPath SourcePackagesCache" - - uses: actions/cache@v2 with: path: | @@ -43,6 +37,12 @@ jobs: restore-keys: | ${{ runner.os }}-spm1- + - name: Resolve Dependencies + shell: bash + run: " + xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme DApp -clonedSourcePackagesDirPath SourcePackagesCache; \ + xcodebuild -resolvePackageDependencies -project Example/ExampleApp.xcodeproj -scheme WalletConnect -clonedSourcePackagesDirPath SourcePackagesCache" + - uses: ./.github/actions/ci with: type: ${{ matrix.test-type }} From 272e0051b3351bbdb24a42421c9d8e0a16db165c Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 27 Sep 2022 09:22:44 +0600 Subject: [PATCH 36/40] Update chache id --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fe608f08c..663d41e88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,9 +33,9 @@ jobs: path: | .build SourcePackagesCache - key: ${{ runner.os }}-spm1-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-spm2-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm1- + ${{ runner.os }}-spm2- - name: Resolve Dependencies shell: bash @@ -65,9 +65,9 @@ jobs: path: | .build SourcePackagesCache - key: ${{ runner.os }}-spm1-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-spm2-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm1- + ${{ runner.os }}-spm2- - uses: ./.github/actions/ci with: From f58a867a39213cf0ccab22844c5078ad2503a38f Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 27 Sep 2022 09:43:08 +0600 Subject: [PATCH 37/40] testHandleSessionProposeResponse stability --- Tests/TestingUtils/NetworkingInteractorMock.swift | 3 +++ Tests/WalletConnectSignTests/PairingEngineTests.swift | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 0ba467bab..b37b3d712 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -22,6 +22,8 @@ public class NetworkingInteractorMock: NetworkInteracting { private(set) var requestCallCount = 0 var didCallRequest: Bool { requestCallCount > 0 } + var onSubscribeCalled: (() -> Void)? + public let socketConnectionStatusPublisherSubject = PassthroughSubject() public var socketConnectionStatusPublisher: AnyPublisher { socketConnectionStatusPublisherSubject.eraseToAnyPublisher() @@ -79,6 +81,7 @@ public class NetworkingInteractorMock: NetworkInteracting { } public func subscribe(topic: String) async throws { + defer { onSubscribeCalled?() } subscriptions.append(topic) didCallSubscribe = true } diff --git a/Tests/WalletConnectSignTests/PairingEngineTests.swift b/Tests/WalletConnectSignTests/PairingEngineTests.swift index 6bd6538cc..0f99d16d0 100644 --- a/Tests/WalletConnectSignTests/PairingEngineTests.swift +++ b/Tests/WalletConnectSignTests/PairingEngineTests.swift @@ -87,6 +87,7 @@ final class PairingEngineTests: XCTestCase { } func testHandleSessionProposeResponse() async { + let exp = expectation(description: "testHandleSessionProposeResponse") let uri = try! await engine.create() let pairing = storageMock.getPairing(forTopic: uri.topic)! let topicA = pairing.topic @@ -107,10 +108,17 @@ final class PairingEngineTests: XCTestCase { let response = RPCResponse(id: request.id!, result: RPCResult.response(AnyCodable(proposalResponse))) + networkingInteractor.onSubscribeCalled = { + exp.fulfill() + } + networkingInteractor.responsePublisherSubject.send((topicA, request, response)) let privateKey = try! cryptoMock.getPrivateKey(for: proposal.proposer.publicKey)! let topicB = deriveTopic(publicKey: responder.publicKey, privateKey: privateKey) let storedPairing = storageMock.getPairing(forTopic: topicA)! + + wait(for: [exp], timeout: 5) + let sessionTopic = networkingInteractor.subscriptions.last! XCTAssertTrue(networkingInteractor.didCallSubscribe) From ba9019369a39da4c17dea224ed0381613d074b67 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 27 Sep 2022 09:44:03 +0600 Subject: [PATCH 38/40] Update cache id --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 663d41e88..5baea21a0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,9 +33,9 @@ jobs: path: | .build SourcePackagesCache - key: ${{ runner.os }}-spm2-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-spm3-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm2- + ${{ runner.os }}-spm3- - name: Resolve Dependencies shell: bash @@ -65,9 +65,9 @@ jobs: path: | .build SourcePackagesCache - key: ${{ runner.os }}-spm2-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-spm3-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm2- + ${{ runner.os }}-spm3- - uses: ./.github/actions/ci with: From 79e7adcc1602b02a79635e4dd511f5b10ed25b39 Mon Sep 17 00:00:00 2001 From: Artur Guseinov Date: Tue, 27 Sep 2022 13:50:48 +0600 Subject: [PATCH 39/40] Default cache id --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5baea21a0..a81d10cb4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,9 +33,9 @@ jobs: path: | .build SourcePackagesCache - key: ${{ runner.os }}-spm3-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm3- + ${{ runner.os }}-spm- - name: Resolve Dependencies shell: bash @@ -65,9 +65,9 @@ jobs: path: | .build SourcePackagesCache - key: ${{ runner.os }}-spm3-${{ hashFiles('**/Package.resolved') }} + key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: | - ${{ runner.os }}-spm3- + ${{ runner.os }}-spm- - uses: ./.github/actions/ci with: From 81c4fd71e7e4484f8c45f561d6654af15e59e8e8 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 27 Sep 2022 12:48:00 +0200 Subject: [PATCH 40/40] remove wait clients connected, refactor push protocol method --- .../Pairing/PairingTests.swift | 21 ++------------- .../PairingRequester.swift | 5 ++-- .../PairingRequestsSubscriber.swift | 2 +- .../Push/PushClient.swift | 8 +++--- .../Push/PushProposeProtocolMethod.swift | 12 +++++++++ .../Push/PushProtocolMethod.swift | 26 ------------------- 6 files changed, 23 insertions(+), 51 deletions(-) create mode 100644 Sources/WalletConnectPairing/Push/PushProposeProtocolMethod.swift delete mode 100644 Sources/WalletConnectPairing/Push/PushProtocolMethod.swift diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 5a581a215..01085df49 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -20,25 +20,8 @@ final class PairingTests: XCTestCase { private var publishers = [AnyCancellable]() override func setUp() { - (appPairingClient, appPushClient) = makeClients(prefix: "👻 App") - (walletPairingClient, walletPushClient) = makeClients(prefix: "🤑 Wallet") - - let expectation = expectation(description: "Wait Clients Connected") - expectation.expectedFulfillmentCount = 2 - - appPairingClient.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - walletPairingClient.socketConnectionStatusPublisher.sink { status in - if status == .connected { - expectation.fulfill() - } - }.store(in: &publishers) - - wait(for: [expectation], timeout: 5) + (appPairingClient, appPushClient) = makeClients(prefix: "🤖 App") + (walletPairingClient, walletPushClient) = makeClients(prefix: "🐶 Wallet") } func makeClients(prefix: String) -> (PairingClient, PushClient) { diff --git a/Sources/WalletConnectPairing/PairingRequester.swift b/Sources/WalletConnectPairing/PairingRequester.swift index bdc89d2dc..535f9eb34 100644 --- a/Sources/WalletConnectPairing/PairingRequester.swift +++ b/Sources/WalletConnectPairing/PairingRequester.swift @@ -20,7 +20,8 @@ public class PushProposer { } func request(topic: String, params: AnyCodable) async throws { - let request = RPCRequest(method: PushProtocolMethod.propose.method, params: params) - try await networkingInteractor.requestNetworkAck(request, topic: topic, tag: PushProtocolMethod.propose.requestTag) + let protocolMethod = PushProposeProtocolMethod() + let request = RPCRequest(method: protocolMethod.method, params: params) + try await networkingInteractor.requestNetworkAck(request, topic: topic, protocolMethod: protocolMethod) } } diff --git a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift index f997b886f..ed3ede277 100644 --- a/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift +++ b/Sources/WalletConnectPairing/PairingRequestsSubscriber.swift @@ -27,7 +27,7 @@ public class PairingRequestsSubscriber { .sink { [unowned self] topic, request in Task(priority: .high) { // TODO - spec tag - try await networkingInteractor.respondError(topic: topic, requestId: request.id!, tag: 123456, reason: PairError.methodUnsupported) + try await networkingInteractor.respondError(topic: topic, requestId: request.id!, protocolMethod: protocolMethod, reason: PairError.methodUnsupported) } }.store(in: &publishers) diff --git a/Sources/WalletConnectPairing/Push/PushClient.swift b/Sources/WalletConnectPairing/Push/PushClient.swift index 4739593e5..5dee1b892 100644 --- a/Sources/WalletConnectPairing/Push/PushClient.swift +++ b/Sources/WalletConnectPairing/Push/PushClient.swift @@ -42,14 +42,16 @@ public class PushClient { private extension PushClient { func setupPairingSubscriptions() { - pairingRegisterer.register(method: PushProtocolMethod.propose) + let protocolMethod = PushProposeProtocolMethod() - networkInteractor.responseErrorSubscription(on: PushProtocolMethod.propose) + pairingRegisterer.register(method: protocolMethod) + + networkInteractor.responseErrorSubscription(on: protocolMethod) .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in logger.error(payload.error.localizedDescription) }.store(in: &publishers) - networkInteractor.requestSubscription(on: PushProtocolMethod.propose) + networkInteractor.requestSubscription(on: protocolMethod) .sink { [unowned self] (payload: RequestSubscriptionPayload) in requestPublisherSubject.send((payload.topic, payload.request)) }.store(in: &publishers) diff --git a/Sources/WalletConnectPairing/Push/PushProposeProtocolMethod.swift b/Sources/WalletConnectPairing/Push/PushProposeProtocolMethod.swift new file mode 100644 index 000000000..1c5e62906 --- /dev/null +++ b/Sources/WalletConnectPairing/Push/PushProposeProtocolMethod.swift @@ -0,0 +1,12 @@ +import Foundation +import WalletConnectNetworking + +struct PushProposeProtocolMethod: ProtocolMethod { + let method: String = "wc_pushPropose" + + let requestConfig: RelayConfig = RelayConfig(tag: 111, prompt: true, ttl: 300) + + let responseConfig: RelayConfig = RelayConfig(tag: 112, prompt: true, ttl: 300) +} + +struct PushRequestParams: Codable {} diff --git a/Sources/WalletConnectPairing/Push/PushProtocolMethod.swift b/Sources/WalletConnectPairing/Push/PushProtocolMethod.swift deleted file mode 100644 index 0b6772da0..000000000 --- a/Sources/WalletConnectPairing/Push/PushProtocolMethod.swift +++ /dev/null @@ -1,26 +0,0 @@ -import Foundation -import WalletConnectNetworking - -enum PushProtocolMethod: String, ProtocolMethod { - case propose = "wc_pushPropose" - - var method: String { - return self.rawValue - } - - var requestTag: Int { - switch self { - case .propose: - return 3000 - } - } - - var responseTag: Int { - switch self { - case .propose: - return 3001 - } - } -} - -struct PushRequestParams: Codable {}