diff --git a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 465b92c16..f11eb5be8 100644 --- a/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Example/ExampleApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -33,7 +33,7 @@ "repositoryURL": "https://github.com/mixpanel/mixpanel-swift", "state": { "branch": "master", - "revision": "1ce27d937009d5ecce74dad97d69898ffea49c75", + "revision": "c6336881a077d283db6f6d6e2f242977250230bd", "version": null } }, @@ -69,8 +69,8 @@ "repositoryURL": "https://github.com/getsentry/sentry-cocoa.git", "state": { "branch": null, - "revision": "14aa6e47b03b820fd2b338728637570b9e969994", - "version": "8.12.0" + "revision": "74cf23b2946c92550fb1185612077151497e648e", + "version": "8.17.1" } }, { diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index bbea6aa46..657530902 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -23,13 +23,11 @@ final class PairingTests: XCTestCase { private var publishers = [AnyCancellable]() - func makeClientDependencies(prefix: String) -> (PairingClient, NetworkingInteractor, KeychainStorageProtocol, KeyValueStorage) { + func makeClients(prefix: String, includeAuth: Bool = true) -> (PairingClient, AuthClient?) { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() - let relayLogger = ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug) - let pairingLogger = ConsoleLogger(prefix: prefix + " [Pairing]", loggingLevel: .debug) - let networkingLogger = ConsoleLogger(prefix: prefix + " [Networking]", loggingLevel: .debug) + let logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug) let relayClient = RelayClientFactory.create( relayHost: InputConfig.relayHost, @@ -37,81 +35,49 @@ final class PairingTests: XCTestCase { keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, socketFactory: DefaultSocketFactory(), - logger: relayLogger) + logger: logger) let networkingClient = NetworkingClientFactory.create( relayClient: relayClient, - logger: networkingLogger, + logger: logger, keychainStorage: keychain, keyValueStorage: keyValueStorage) let pairingClient = PairingClientFactory.create( - logger: pairingLogger, + logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychain, networkingClient: networkingClient) - let clientId = try! networkingClient.getClientId() - networkingLogger.debug("My client id is: \(clientId)") - - return (pairingClient, networkingClient, keychain, keyValueStorage) - } - - func makeDappClients() { - let prefix = "🤖 Dapp: " - let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) - let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug) - appPairingClient = pairingClient - - appAuthClient = AuthClientFactory.create( - metadata: AppMetadata(name: name, description: "", url: "", icons: [""], redirect: AppMetadata.Redirect(native: "wcdapp://", universal: nil)), - projectId: InputConfig.projectId, - crypto: DefaultCryptoProvider(), - logger: notifyLogger, - keyValueStorage: keyValueStorage, - keychainStorage: keychain, - networkingClient: networkingInteractor, - pairingRegisterer: pairingClient, - iatProvider: IATProviderMock()) - } - func makeWalletClients() { - let prefix = "🐶 Wallet: " - let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) - let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug) - let defaults = RuntimeKeyValueStorage() - walletPairingClient = pairingClient - let historyClient = HistoryClientFactory.create( - historyUrl: "https://history.walletconnect.com", - relayUrl: "wss://relay.walletconnect.com", - keyValueStorage: defaults, - keychain: keychain, - logger: notifyLogger - ) - appAuthClient = AuthClientFactory.create( - metadata: AppMetadata(name: name, description: "", url: "", icons: [""], redirect: AppMetadata.Redirect(native: "", universal: nil)), - projectId: InputConfig.projectId, - crypto: DefaultCryptoProvider(), - logger: notifyLogger, - keyValueStorage: keyValueStorage, - keychainStorage: keychain, - networkingClient: networkingInteractor, - pairingRegisterer: pairingClient, - iatProvider: IATProviderMock()) - } - func makeWalletPairingClient() { - let prefix = "🐶 Wallet: " - let (pairingClient, _, _, _) = makeClientDependencies(prefix: prefix) - walletPairingClient = pairingClient + let clientId = try! networkingClient.getClientId() + logger.debug("My client id is: \(clientId)") + + if includeAuth { + let authClient = AuthClientFactory.create( + metadata: AppMetadata(name: name, description: "", url: "", icons: [""], redirect: AppMetadata.Redirect(native: "", universal: nil)), + projectId: InputConfig.projectId, + crypto: DefaultCryptoProvider(), + logger: logger, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + networkingClient: networkingClient, + pairingRegisterer: pairingClient, + iatProvider: IATProviderMock()) + + return (pairingClient, authClient) + } else { + return (pairingClient, nil) + } } override func setUp() { - makeDappClients() + (appPairingClient, appAuthClient) = makeClients(prefix: "🤖 Dapp: ") + (walletPairingClient, _) = makeClients(prefix: "🐶 Wallet: ", includeAuth: false) } func testPing() async { let expectation = expectation(description: "expects ping response") - makeWalletClients() let uri = try! await appPairingClient.create() try? await walletPairingClient.pair(uri: uri) try! await walletPairingClient.ping(topic: uri.topic) @@ -124,7 +90,6 @@ final class PairingTests: XCTestCase { } func testResponseErrorForMethodUnregistered() async { - makeWalletPairingClient() let expectation = expectation(description: "wallet responds unsupported method for unregistered method") appAuthClient.authResponsePublisher.sink { (_, response) in @@ -134,14 +99,27 @@ final class PairingTests: XCTestCase { let uri = try! await appPairingClient.create() - try? await walletPairingClient.pair(uri: uri) + try! await walletPairingClient.pair(uri: uri) try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) wait(for: [expectation], timeout: InputConfig.defaultTimeout) } - func testDisconnect() { - // TODO + func testDisconnect() async { + + let expectation = expectation(description: "wallet disconnected pairing") + + + walletPairingClient.pairingDeletePublisher.sink { _ in + expectation.fulfill() + }.store(in: &publishers) + + let uri = try! await appPairingClient.create() + + try? await walletPairingClient.pair(uri: uri) + + try! await appPairingClient.disconnect(topic: uri.topic) + wait(for: [expectation], timeout: InputConfig.defaultTimeout) } } diff --git a/Sources/WalletConnectPairing/PairingClient.swift b/Sources/WalletConnectPairing/PairingClient.swift index c9ca77413..16d8f0076 100644 --- a/Sources/WalletConnectPairing/PairingClient.swift +++ b/Sources/WalletConnectPairing/PairingClient.swift @@ -5,6 +5,10 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient public var pingResponsePublisher: AnyPublisher<(String), Never> { pingResponsePublisherSubject.eraseToAnyPublisher() } + public var pairingDeletePublisher: AnyPublisher<(code: Int, message: String), Never> { + pairingDeleteRequestSubscriber.deletePublisherSubject.eraseToAnyPublisher() + } + public let socketConnectionStatusPublisher: AnyPublisher private let pairingStorage: WCPairingStorage @@ -17,9 +21,10 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient private let networkingInteractor: NetworkInteracting private let pairingRequestsSubscriber: PairingRequestsSubscriber private let pairingsProvider: PairingsProvider - private let deletePairingService: DeletePairingService + private let pairingDeleteRequester: PairingDeleteRequester private let resubscribeService: PairingResubscribeService private let expirationService: ExpirationService + private let pairingDeleteRequestSubscriber: PairingDeleteRequestSubscriber private let cleanupService: PairingCleanupService @@ -33,7 +38,8 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient networkingInteractor: NetworkInteracting, logger: ConsoleLogging, walletPairService: WalletPairService, - deletePairingService: DeletePairingService, + pairingDeleteRequester: PairingDeleteRequester, + pairingDeleteRequestSubscriber: PairingDeleteRequestSubscriber, resubscribeService: PairingResubscribeService, expirationService: ExpirationService, pairingRequestsSubscriber: PairingRequestsSubscriber, @@ -49,7 +55,8 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient self.networkingInteractor = networkingInteractor self.socketConnectionStatusPublisher = socketConnectionStatusPublisher self.logger = logger - self.deletePairingService = deletePairingService + self.pairingDeleteRequester = pairingDeleteRequester + self.pairingDeleteRequestSubscriber = pairingDeleteRequestSubscriber self.appPairActivateService = appPairActivateService self.resubscribeService = resubscribeService self.expirationService = expirationService @@ -112,7 +119,7 @@ public class PairingClient: PairingRegisterer, PairingInteracting, PairingClient } public func disconnect(topic: String) async throws { - try await deletePairingService.delete(topic: topic) + try await pairingDeleteRequester.delete(topic: topic) } public func validatePairingExistance(_ topic: String) throws { diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift index d24b76adb..902ba7d28 100644 --- a/Sources/WalletConnectPairing/PairingClientFactory.swift +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -30,11 +30,12 @@ public struct PairingClientFactory { let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingClient, pairingStorage: pairingStore, logger: logger) let pairingsProvider = PairingsProvider(pairingStorage: pairingStore) let cleanupService = PairingCleanupService(pairingStore: pairingStore, kms: kms) - let deletePairingService = DeletePairingService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, logger: logger) + let pairingDeleteRequester = PairingDeleteRequester(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, logger: logger) let pingService = PairingPingService(pairingStorage: pairingStore, networkingInteractor: networkingClient, logger: logger) let appPairActivateService = AppPairActivationService(pairingStorage: pairingStore, logger: logger) let expirationService = ExpirationService(pairingStorage: pairingStore, networkInteractor: networkingClient, kms: kms) let resubscribeService = PairingResubscribeService(networkInteractor: networkingClient, pairingStorage: pairingStore) + let pairingDeleteRequestSubscriber = PairingDeleteRequestSubscriber(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, logger: logger) return PairingClient( pairingStorage: pairingStore, @@ -42,7 +43,8 @@ public struct PairingClientFactory { networkingInteractor: networkingClient, logger: logger, walletPairService: walletPairService, - deletePairingService: deletePairingService, + pairingDeleteRequester: pairingDeleteRequester, + pairingDeleteRequestSubscriber: pairingDeleteRequestSubscriber, resubscribeService: resubscribeService, expirationService: expirationService, pairingRequestsSubscriber: pairingRequestsSubscriber, diff --git a/Sources/WalletConnectPairing/PairingClientProtocol.swift b/Sources/WalletConnectPairing/PairingClientProtocol.swift index e025f7fd6..7edd05b30 100644 --- a/Sources/WalletConnectPairing/PairingClientProtocol.swift +++ b/Sources/WalletConnectPairing/PairingClientProtocol.swift @@ -2,6 +2,7 @@ import Combine public protocol PairingClientProtocol { var logsPublisher: AnyPublisher {get} + var pairingDeletePublisher: AnyPublisher<(code: Int, message: String), Never> {get} func pair(uri: WalletConnectURI) async throws func disconnect(topic: String) async throws func getPairings() -> [Pairing] diff --git a/Sources/WalletConnectPairing/RPCParams/PairingDeleteParams.swift b/Sources/WalletConnectPairing/RPCParams/PairingDeleteParams.swift new file mode 100644 index 000000000..6f3495514 --- /dev/null +++ b/Sources/WalletConnectPairing/RPCParams/PairingDeleteParams.swift @@ -0,0 +1,7 @@ + +import Foundation + +struct PairingDeleteParams: Codable { + let code: Int + let message: String +} diff --git a/Sources/WalletConnectPairing/Services/Common/Delete/PairingDeleteRequestSubscriber.swift b/Sources/WalletConnectPairing/Services/Common/Delete/PairingDeleteRequestSubscriber.swift new file mode 100644 index 000000000..ab81a5266 --- /dev/null +++ b/Sources/WalletConnectPairing/Services/Common/Delete/PairingDeleteRequestSubscriber.swift @@ -0,0 +1,43 @@ +import Combine + +public final class PairingDeleteRequestSubscriber { + private let networkingInteractor: NetworkInteracting + private let logger: ConsoleLogging + private let pairingStorage: WCPairingStorage + private let kms: KeyManagementServiceProtocol + let deletePublisherSubject = PassthroughSubject<(code: Int, message: String), Never>() + + private var publishers = [AnyCancellable]() + + public init( + networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + pairingStorage: WCPairingStorage, + logger: ConsoleLogging + ) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.pairingStorage = pairingStorage + self.logger = logger + subscribeDeleteRequest() + } + + private func subscribeDeleteRequest() { + let method = PairingProtocolMethod.delete + networkingInteractor.requestSubscription(on: method) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + + let topic = payload.topic + logger.debug("Received pairing delete request") + pairingStorage.delete(topic: topic) + kms.deleteSymmetricKey(for: topic) + networkingInteractor.unsubscribe(topic: topic) + + deletePublisherSubject.send((code: payload.request.code, message: payload.request.message)) + Task(priority: .high) { + try? await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: method) + } + } + .store(in: &publishers) + } +} diff --git a/Sources/WalletConnectPairing/Services/Common/DeletePairingService.swift b/Sources/WalletConnectPairing/Services/Common/Delete/PairingDeleteRequester.swift similarity index 87% rename from Sources/WalletConnectPairing/Services/Common/DeletePairingService.swift rename to Sources/WalletConnectPairing/Services/Common/Delete/PairingDeleteRequester.swift index afa43e517..f976a3cd0 100644 --- a/Sources/WalletConnectPairing/Services/Common/DeletePairingService.swift +++ b/Sources/WalletConnectPairing/Services/Common/Delete/PairingDeleteRequester.swift @@ -1,6 +1,6 @@ import Foundation -class DeletePairingService { +class PairingDeleteRequester { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let pairingStorage: WCPairingStorage @@ -19,8 +19,9 @@ class DeletePairingService { func delete(topic: String) async throws { let reason = PairingReasonCode.userDisconnected let protocolMethod = PairingProtocolMethod.delete + let pairingDeleteParams = PairingDeleteParams(code: reason.code, message: reason.message) logger.debug("Will delete pairing for reason: message: \(reason.message) code: \(reason.code)") - let request = RPCRequest(method: protocolMethod.method, params: reason) + let request = RPCRequest(method: protocolMethod.method, params: pairingDeleteParams) try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) pairingStorage.delete(topic: topic) kms.deleteSymmetricKey(for: topic) diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 7a3b9ddc7..5b70a374a 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -63,6 +63,10 @@ public class Web3WalletClient { signClient.sessionResponsePublisher.eraseToAnyPublisher() } + public var pairingDeletePublisher: AnyPublisher<(code: Int, message: String), Never> { + pairingClient.pairingDeletePublisher + } + public var logsPublisher: AnyPublisher { return signClient.logsPublisher .merge(with: pairingClient.logsPublisher) diff --git a/Tests/Web3WalletTests/Mocks/PairingClientMock.swift b/Tests/Web3WalletTests/Mocks/PairingClientMock.swift index edf3f1390..0f0012d59 100644 --- a/Tests/Web3WalletTests/Mocks/PairingClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/PairingClientMock.swift @@ -4,7 +4,14 @@ import Combine @testable import WalletConnectPairing final class PairingClientMock: PairingClientProtocol { - private var logsSubject = PassthroughSubject() + var pairingDeletePublisher: AnyPublisher<(code: Int, message: String), Never> { + pairingDeletePublisherSubject.eraseToAnyPublisher() + } + + var pairingDeletePublisherSubject = PassthroughSubject<(code: Int, message: String), Never>() + + + var logsSubject = PassthroughSubject() var logsPublisher: AnyPublisher { return logsSubject.eraseToAnyPublisher()