diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 30194ce37..ea759adfe 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -6,6 +6,7 @@ public protocol NetworkInteracting { var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest), Never> { get } func subscribe(topic: String) async throws func unsubscribe(topic: String) + func batchUnsubscribe(topics: [String]) async throws func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws func requestNetworkAck(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod) async throws func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 236af5e54..9e72ba110 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -56,6 +56,11 @@ public class NetworkingInteractor: NetworkInteracting { } } + public func batchUnsubscribe(topics: [String]) async throws { + try await relayClient.batchUnsubscribe(topics: topics) + rpcHistory.deleteAll(forTopics: topics) + } + public func requestSubscription(on request: ProtocolMethod) -> AnyPublisher, Never> { return requestPublisher .filter { rpcRequest in diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index a55c1c941..1a618bda3 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -194,9 +194,31 @@ public final class RelayClient { subscribe(topic: topic) { error in if let error = error { continuation.resume(throwing: error) - return + } else { + continuation.resume(returning: ()) + } + } + } + } + + public func unsubscribe(topic: String) async throws { + return try await withCheckedThrowingContinuation { continuation in + unsubscribe(topic: topic) { error in + if let error = error { + continuation.resume(throwing: error) + } else { + continuation.resume(returning: ()) + } + } + } + } + + public func batchUnsubscribe(topics: [String]) async throws { + await withThrowingTaskGroup(of: Void.self) { group in + for topic in topics { + group.addTask { + try await self.unsubscribe(topic: topic) } - continuation.resume(returning: ()) } } } diff --git a/Sources/WalletConnectSign/Services/SignCleanupService.swift b/Sources/WalletConnectSign/Services/SignCleanupService.swift index 9142e0183..1da2aee3e 100644 --- a/Sources/WalletConnectSign/Services/SignCleanupService.swift +++ b/Sources/WalletConnectSign/Services/SignCleanupService.swift @@ -6,15 +6,36 @@ final class SignCleanupService { private let sessionStore: WCSessionStorage private let kms: KeyManagementServiceProtocol private let sessionToPairingTopic: CodableStore + private let networkInteractor: NetworkInteracting - init(pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, kms: KeyManagementServiceProtocol, sessionToPairingTopic: CodableStore) { + init(pairingStore: WCPairingStorage, sessionStore: WCSessionStorage, kms: KeyManagementServiceProtocol, sessionToPairingTopic: CodableStore, networkInteractor: NetworkInteracting) { self.pairingStore = pairingStore self.sessionStore = sessionStore self.sessionToPairingTopic = sessionToPairingTopic + self.networkInteractor = networkInteractor self.kms = kms } + func cleanup() async throws { + try await unsubscribe() + try cleanupStorages() + } + func cleanup() throws { + try cleanupStorages() + } +} + +private extension SignCleanupService { + + func unsubscribe() async throws { + let pairing = pairingStore.getAll().map { $0.topic } + let session = sessionStore.getAll().map { $0.topic } + + try await networkInteractor.batchUnsubscribe(topics: pairing + session) + } + + func cleanupStorages() throws { pairingStore.deleteAll() sessionStore.deleteAll() sessionToPairingTopic.deleteAll() diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index e00d55c98..dda1b477e 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -346,6 +346,13 @@ public final class SignClient { return Request(id: record.id, topic: record.topic, method: record.request.method, params: request, chainId: request.chainId) } + /// Delete all stored data such as: pairings, sessions, keys + /// + /// - Note: Will unsubscribe from all topics + public func cleanup() async throws { + try await cleanupService.cleanup() + } + #if DEBUG /// Delete all stored data such as: pairings, sessions, keys /// diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 9fd5c17f4..ce27c1e4c 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -29,7 +29,7 @@ public struct SignClientFactory { let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let approveEngine = ApproveEngine(networkingInteractor: networkingClient, proposalPayloadsStore: proposalPayloadsStore, sessionToPairingTopic: sessionToPairingTopic, pairingRegisterer: pairingClient, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore) - let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionToPairingTopic: sessionToPairingTopic) + let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionToPairingTopic: sessionToPairingTopic, networkInteractor: networkingClient) let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let disconnectService = DisconnectService(deleteSessionService: deleteSessionService, sessionStorage: sessionStore) let sessionPingService = SessionPingService(sessionStorage: sessionStore, networkingInteractor: networkingClient, logger: logger) diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index c5cf9b0df..8e130c106 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -57,14 +57,18 @@ public final class RPCHistory { return record } - public func deleteAll(forTopic topic: String) { + public func deleteAll(forTopics topics: [String]){ storage.getAll().forEach { record in - if record.topic == topic { + if topics.contains(record.topic) { storage.delete(forKey: "\(record.id)") } } } + public func deleteAll(forTopic topic: String) { + deleteAll(forTopics: [topic]) + } + public func getPending() -> [Record] { storage.getAll().filter {$0.response == nil} } diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 0ed0453bf..323c52279 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -99,6 +99,12 @@ public class NetworkingInteractorMock: NetworkInteracting { didCallUnsubscribe = true } + public func batchUnsubscribe(topics: [String]) async throws { + for topic in topics { + unsubscribe(topic: topic) + } + } + public func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws { requestCallCount += 1 requests.append((topic, request))