diff --git a/Example/Shared/Signer/ETHSigner.swift b/Example/Shared/Signer/ETHSigner.swift index dfe84420b..232dcffc2 100644 --- a/Example/Shared/Signer/ETHSigner.swift +++ b/Example/Shared/Signer/ETHSigner.swift @@ -3,7 +3,6 @@ import Commons import Web3 struct ETHSigner { - private let importAccount: ImportAccount init(importAccount: ImportAccount) { @@ -21,8 +20,21 @@ struct ETHSigner { func personalSign(_ params: AnyCodable) -> AnyCodable { let params = try! params.get([String].self) let messageToSign = params[0] - let dataToHash = dataToHash(messageToSign) - let (v, r, s) = try! privateKey.sign(message: .init(hex: dataToHash.toHexString())) + + // Determine if the message is hex-encoded or plain text + let dataToSign: Bytes + if messageToSign.hasPrefix("0x") { + // Hex-encoded message, remove "0x" and convert + let messageData = Data(hex: String(messageToSign.dropFirst(2))) + dataToSign = dataToHash(messageData) + } else { + // Plain text message, convert directly to data + let messageData = Data(messageToSign.utf8) + dataToSign = dataToHash(messageData) + } + + // Sign the data + let (v, r, s) = try! privateKey.sign(message: .init(Data(dataToSign))) let result = "0x" + r.toHexString() + s.toHexString() + String(v + 27, radix: 16) return AnyCodable(result) } @@ -45,12 +57,10 @@ struct ETHSigner { return AnyCodable(result) } - private func dataToHash(_ message: String) -> Bytes { + private func dataToHash(_ data: Data) -> Bytes { let prefix = "\u{19}Ethereum Signed Message:\n" - let messageData = Data(hex: message) - let prefixData = (prefix + String(messageData.count)).data(using: .utf8)! - let prefixedMessageData = prefixData + messageData - let dataToHash: Bytes = .init(hex: prefixedMessageData.toHexString()) - return dataToHash + let prefixData = (prefix + String(data.count)).data(using: .utf8)! + let prefixedMessageData = prefixData + data + return .init(hex: prefixedMessageData.toHexString()) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift index a9e9efa10..74e141979 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionRequest/SessionRequestPresenter.swift @@ -13,10 +13,18 @@ final class SessionRequestPresenter: ObservableObject { let validationStatus: VerifyContext.ValidationStatus? var message: String { - let message = try? sessionRequest.params.get([String].self) - let decryptedMessage = message.map { String(data: Data(hex: $0.first ?? ""), encoding: .utf8) } - return (decryptedMessage ?? String(describing: sessionRequest.params.value)) ?? String(describing: sessionRequest.params.value) + guard let messages = try? sessionRequest.params.get([String].self), + let firstMessage = messages.first else { + return String(describing: sessionRequest.params.value) + } + + // Attempt to decode the message if it's hex-encoded + let decodedMessage = String(data: Data(hex: firstMessage), encoding: .utf8) + + // Return the decoded message if available, else return the original message + return decodedMessage?.isEmpty == false ? decodedMessage! : firstMessage } + @Published var showError = false @Published var errorMessage = "Error" diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index 6042e0366..7ff218e84 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -103,9 +103,12 @@ public class NotifyClient { } public func unregister(account: Account) async throws { - try await identityClient.unregister(account: account) + if identityClient.isIdentityRegistered(account: account) { + try await identityClient.unregister(account: account) + } notifyWatcherAgreementKeysProvider.removeAgreement(account: account) try notifyStorage.clearDatabase(account: account) + try await pushClient.unregister() notifyAccountProvider.logout() subscriptionWatcher.stop() } diff --git a/Sources/WalletConnectPush/PushClient.swift b/Sources/WalletConnectPush/PushClient.swift index 2c378b3e6..8d50aff12 100644 --- a/Sources/WalletConnectPush/PushClient.swift +++ b/Sources/WalletConnectPush/PushClient.swift @@ -3,14 +3,18 @@ import Combine public class PushClient: PushClientProtocol { private let registerService: PushRegisterService + private let unregisterService: UnregisterService private let logger: ConsoleLogging public var logsPublisher: AnyPublisher { return logger.logsPublisher } - init(registerService: PushRegisterService, logger: ConsoleLogging) { + init(registerService: PushRegisterService, + logger: ConsoleLogging, + unregisterService: UnregisterService) { self.registerService = registerService + self.unregisterService = unregisterService self.logger = logger } @@ -18,6 +22,10 @@ public class PushClient: PushClientProtocol { try await registerService.register(deviceToken: deviceToken, alwaysRaw: enableEncrypted) } + public func unregister() async throws { + try await unregisterService.unregister() + } + #if DEBUG public func register(deviceToken: String) async throws { try await registerService.register(deviceToken: deviceToken, alwaysRaw: true) diff --git a/Sources/WalletConnectPush/PushClientFactory.swift b/Sources/WalletConnectPush/PushClientFactory.swift index 8a7a871cb..ab1911d95 100644 --- a/Sources/WalletConnectPush/PushClientFactory.swift +++ b/Sources/WalletConnectPush/PushClientFactory.swift @@ -42,7 +42,8 @@ public struct PushClientFactory { let pushAuthenticator = PushAuthenticator(clientIdStorage: clientIdStorage, pushHost: pushHost) let registerService = PushRegisterService(httpClient: httpClient, projectId: projectId, clientIdStorage: clientIdStorage, pushAuthenticator: pushAuthenticator, logger: logger, environment: environment) + let unregisterService = UnregisterService(httpClient: httpClient, projectId: projectId, clientIdStorage: clientIdStorage, pushAuthenticator: pushAuthenticator, logger: logger, pushHost: pushHost, environment: environment) - return PushClient(registerService: registerService, logger: logger) + return PushClient(registerService: registerService, logger: logger, unregisterService: unregisterService) } } diff --git a/Sources/WalletConnectPush/UnregisterService.swift b/Sources/WalletConnectPush/UnregisterService.swift new file mode 100644 index 000000000..653bb6719 --- /dev/null +++ b/Sources/WalletConnectPush/UnregisterService.swift @@ -0,0 +1,55 @@ +import Foundation + +actor UnregisterService { + private let httpClient: HTTPClient + private let projectId: String + private let logger: ConsoleLogging + private let environment: APNSEnvironment + private let pushAuthenticator: PushAuthenticating + private let clientIdStorage: ClientIdStoring + private let pushHost: String + + init(httpClient: HTTPClient, + projectId: String, + clientIdStorage: ClientIdStoring, + pushAuthenticator: PushAuthenticating, + logger: ConsoleLogging, + pushHost: String, + environment: APNSEnvironment) { + self.httpClient = httpClient + self.clientIdStorage = clientIdStorage + self.pushAuthenticator = pushAuthenticator + self.projectId = projectId + self.logger = logger + self.pushHost = pushHost + self.environment = environment + } + + func unregister() async throws { + let pushAuthToken = try pushAuthenticator.createAuthToken() + let clientId = try clientIdStorage.getClientId() + + guard let url = URL(string: "https://\(pushHost)/\(projectId)/clients/\(clientId)") else { + logger.error("Invalid URL") + return + } + + var request = URLRequest(url: url) + request.httpMethod = "DELETE" + request.addValue("\(pushAuthToken)", forHTTPHeaderField: "Authorization") + + do { + let (_, response) = try await URLSession.shared.data(for: request) + + guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else { + logger.error("Failed to unregister from Push Server") + throw NSError(domain: "UnregisterService", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to unregister"]) + } + + logger.debug("Successfully unregistered from Push Server") + } catch { + logger.error("Push Server unregistration error: \(error.localizedDescription)") + throw error + } + } +} diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 42dfeb72c..66c4ff16d 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.18.7"} +{"version": "1.18.8"} diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift index 435f34f98..48964f333 100644 --- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift @@ -19,23 +19,25 @@ final class SessionEngine { private let sessionStore: WCSessionStorage private let networkingInteractor: NetworkInteracting - private let historyService: HistoryService + private let historyService: HistoryServiceProtocol private let verifyContextStore: CodableStore private let verifyClient: VerifyClientProtocol private let kms: KeyManagementServiceProtocol private var publishers = [AnyCancellable]() private let logger: ConsoleLogging private let sessionRequestsProvider: SessionRequestsProvider + private let invalidRequestsSanitiser: InvalidRequestsSanitiser init( networkingInteractor: NetworkInteracting, - historyService: HistoryService, + historyService: HistoryServiceProtocol, verifyContextStore: CodableStore, verifyClient: VerifyClientProtocol, kms: KeyManagementServiceProtocol, sessionStore: WCSessionStorage, logger: ConsoleLogging, - sessionRequestsProvider: SessionRequestsProvider + sessionRequestsProvider: SessionRequestsProvider, + invalidRequestsSanitiser: InvalidRequestsSanitiser ) { self.networkingInteractor = networkingInteractor self.historyService = historyService @@ -45,6 +47,7 @@ final class SessionEngine { self.sessionStore = sessionStore self.logger = logger self.sessionRequestsProvider = sessionRequestsProvider + self.invalidRequestsSanitiser = invalidRequestsSanitiser setupConnectionSubscriptions() setupRequestSubscriptions() @@ -52,8 +55,15 @@ final class SessionEngine { setupUpdateSubscriptions() setupExpirationSubscriptions() DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in - sessionRequestsProvider.emitRequestIfPending() + self?.sessionRequestsProvider.emitRequestIfPending() } + + removeInvalidSessionRequests() + } + + private func removeInvalidSessionRequests() { + let sessionTopics = Set(sessionStore.getAll().map(\.topic)) + invalidRequestsSanitiser.removeInvalidSessionRequests(validSessionTopics: sessionTopics) } func hasSession(for topic: String) -> Bool { @@ -192,6 +202,7 @@ private extension SessionEngine { func setupExpirationSubscriptions() { sessionStore.onSessionExpiration = { [weak self] session in + self?.historyService.removePendingRequest(topic: session.topic) self?.kms.deletePrivateKey(for: session.selfParticipant.publicKey) self?.kms.deleteAgreementSecret(for: session.topic) } diff --git a/Sources/WalletConnectSign/RejectionReason.swift b/Sources/WalletConnectSign/RejectionReason.swift index 6044f6075..df5a6a800 100644 --- a/Sources/WalletConnectSign/RejectionReason.swift +++ b/Sources/WalletConnectSign/RejectionReason.swift @@ -6,7 +6,7 @@ public enum RejectionReason { case unsupportedChains case unsupportedMethods case unsupportedAccounts - case upsupportedEvents + case unsupportedEvents } internal extension RejectionReason { @@ -18,7 +18,7 @@ internal extension RejectionReason { return SignReasonCode.unsupportedChains case .unsupportedMethods: return SignReasonCode.userRejectedMethods - case .upsupportedEvents: + case .unsupportedEvents: return SignReasonCode.userRejectedEvents case .unsupportedAccounts: return SignReasonCode.unsupportedAccounts @@ -36,7 +36,7 @@ public extension RejectionReason { case .requiredMethodsNotSatisfied: self = .unsupportedMethods case .requiredEventsNotSatisfied: - self = .upsupportedEvents + self = .unsupportedEvents case .emptySessionNamespacesForbidden: self = .unsupportedAccounts } diff --git a/Sources/WalletConnectSign/Services/HistoryService.swift b/Sources/WalletConnectSign/Services/HistoryService.swift index 6a3b8a01b..c3ac80a3a 100644 --- a/Sources/WalletConnectSign/Services/HistoryService.swift +++ b/Sources/WalletConnectSign/Services/HistoryService.swift @@ -1,6 +1,17 @@ import Foundation -final class HistoryService { +protocol HistoryServiceProtocol { + + func getSessionRequest(id: RPCID) -> (request: Request, context: VerifyContext?)? + + func removePendingRequest(topic: String) + + func getPendingRequests() -> [(request: Request, context: VerifyContext?)] + + func getPendingRequestsSortedByTimestamp() -> [(request: Request, context: VerifyContext?)] +} + +final class HistoryService: HistoryServiceProtocol { private let history: RPCHistory private let verifyContextStore: CodableStore @@ -12,14 +23,20 @@ final class HistoryService { self.history = history self.verifyContextStore = verifyContextStore } - - public func getSessionRequest(id: RPCID) -> (request: Request, context: VerifyContext?)? { + + func getSessionRequest(id: RPCID) -> (request: Request, context: VerifyContext?)? { guard let record = history.get(recordId: id) else { return nil } guard let (request, recordId, _) = mapRequestRecord(record) else { return nil } return (request, try? verifyContextStore.get(key: recordId.string)) } + + func removePendingRequest(topic: String) { + DispatchQueue.global(qos: .background).async { [unowned self] in + history.deleteAll(forTopic: topic) + } + } func getPendingRequests() -> [(request: Request, context: VerifyContext?)] { getPendingRequestsSortedByTimestamp() @@ -80,3 +97,29 @@ private extension HistoryService { return (mappedRequest, record.id, record.timestamp) } } + +#if DEBUG +final class MockHistoryService: HistoryServiceProtocol { + + var removePendingRequestCalled: (String) -> Void = { _ in } + + var pendingRequests: [(request: Request, context: VerifyContext?)] = [] + + func removePendingRequest(topic: String) { + pendingRequests.removeAll(where: { $0.request.topic == topic }) + removePendingRequestCalled(topic) + } + + func getSessionRequest(id: JSONRPC.RPCID) -> (request: Request, context: VerifyContext?)? { + fatalError("Unimplemented") + } + + func getPendingRequests() -> [(request: Request, context: VerifyContext?)] { + pendingRequests + } + + func getPendingRequestsSortedByTimestamp() -> [(request: Request, context: VerifyContext?)] { + fatalError("Unimplemented") + } +} +#endif diff --git a/Sources/WalletConnectSign/Services/InvalidRequestsSanitiser.swift b/Sources/WalletConnectSign/Services/InvalidRequestsSanitiser.swift new file mode 100644 index 000000000..31793769b --- /dev/null +++ b/Sources/WalletConnectSign/Services/InvalidRequestsSanitiser.swift @@ -0,0 +1,20 @@ + +import Foundation + +final class InvalidRequestsSanitiser { + private let historyService: HistoryServiceProtocol + private let history: RPCHistoryProtocol + + init(historyService: HistoryServiceProtocol, history: RPCHistoryProtocol) { + self.historyService = historyService + self.history = history + } + + func removeInvalidSessionRequests(validSessionTopics: Set) { + let pendingRequests = historyService.getPendingRequests() + let invalidTopics = Set(pendingRequests.map { $0.request.topic }).subtracting(validSessionTopics) + if !invalidTopics.isEmpty { + history.deleteAll(forTopics: Array(invalidTopics)) + } + } +} diff --git a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift index 89c8e9e79..3afa8290e 100644 --- a/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift +++ b/Sources/WalletConnectSign/Sign/SessionRequestsProvider.swift @@ -2,7 +2,7 @@ import Combine import Foundation class SessionRequestsProvider { - private let historyService: HistoryService + private let historyService: HistoryServiceProtocol private var sessionRequestPublisherSubject = PassthroughSubject<(request: Request, context: VerifyContext?), Never>() private var lastEmitTime: Date? private let debounceInterval: TimeInterval = 1 @@ -11,7 +11,7 @@ class SessionRequestsProvider { sessionRequestPublisherSubject.eraseToAnyPublisher() } - init(historyService: HistoryService) { + init(historyService: HistoryServiceProtocol) { self.historyService = historyService } diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index bfbdcc6b9..33143fb43 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -61,7 +61,8 @@ public struct SignClientFactory { let historyService = HistoryService(history: rpcHistory, verifyContextStore: verifyContextStore) let verifyClient = VerifyClientFactory.create() let sessionRequestsProvider = SessionRequestsProvider(historyService: historyService) - let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger, sessionRequestsProvider: sessionRequestsProvider) + let invalidRequestsSanitiser = InvalidRequestsSanitiser(historyService: historyService, history: rpcHistory) + let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger, sessionRequestsProvider: sessionRequestsProvider, invalidRequestsSanitiser: invalidRequestsSanitiser) let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) let sessionExtendRequester = SessionExtendRequester(sessionStore: sessionStore, networkingInteractor: networkingClient) diff --git a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift index ff6bda280..cd3b6265c 100644 --- a/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift +++ b/Sources/WalletConnectUtils/RPCHistory/RPCHistory.swift @@ -1,6 +1,13 @@ import Foundation -public final class RPCHistory { +public protocol RPCHistoryProtocol { + + func deleteAll(forTopic topic: String) + + func deleteAll(forTopics topics: [String]) +} + +public final class RPCHistory: RPCHistoryProtocol { public struct Record: Codable { public enum Origin: String, Codable { @@ -144,3 +151,17 @@ extension RPCHistory { } } } + +#if DEBUG +class MockRPCHistory: RPCHistoryProtocol { + var deletedTopics: [String] = [] + + func deleteAll(forTopic topic: String) { + deletedTopics.append(topic) + } + + func deleteAll(forTopics topics: [String]) { + deletedTopics.append(contentsOf: topics) + } +} +#endif diff --git a/Tests/WalletConnectSignTests/InvalidRequestsSanitiserTests.swift b/Tests/WalletConnectSignTests/InvalidRequestsSanitiserTests.swift new file mode 100644 index 000000000..dda89b064 --- /dev/null +++ b/Tests/WalletConnectSignTests/InvalidRequestsSanitiserTests.swift @@ -0,0 +1,69 @@ +import XCTest +@testable import WalletConnectSign +@testable import WalletConnectUtils + +class InvalidRequestsSanitiserTests: XCTestCase { + var sanitiser: InvalidRequestsSanitiser! + var mockHistoryService: MockHistoryService! + var mockRPCHistory: MockRPCHistory! + + override func setUp() { + super.setUp() + mockHistoryService = MockHistoryService() + mockRPCHistory = MockRPCHistory() + sanitiser = InvalidRequestsSanitiser(historyService: mockHistoryService, history: mockRPCHistory) + } + + override func tearDown() { + sanitiser = nil + mockHistoryService = nil + mockRPCHistory = nil + super.tearDown() + } + + func testRemoveInvalidSessionRequests_noPendingRequests() { + let validSessionTopics: Set = ["validTopic1", "validTopic2"] + + sanitiser.removeInvalidSessionRequests(validSessionTopics: validSessionTopics) + + XCTAssertTrue(mockRPCHistory.deletedTopics.isEmpty) + } + + func testRemoveInvalidSessionRequests_allRequestsValid() { + let validSessionTopics: Set = ["validTopic1", "validTopic2"] + mockHistoryService.pendingRequests = [ + (request: try! Request(topic: "validTopic1", method: "method1", params: AnyCodable("params1"), chainId: Blockchain("eip155:1")!), context: nil), + (request: try! Request(topic: "validTopic2", method: "method2", params: AnyCodable("params2"), chainId: Blockchain("eip155:1")!), context: nil) + ] + + sanitiser.removeInvalidSessionRequests(validSessionTopics: validSessionTopics) + + XCTAssertTrue(mockRPCHistory.deletedTopics.isEmpty) + } + + func testRemoveInvalidSessionRequests_someRequestsInvalid() { + let validSessionTopics: Set = ["validTopic1", "validTopic2"] + mockHistoryService.pendingRequests = [ + (request: try! Request(topic: "validTopic1", method: "method1", params: AnyCodable("params1"), chainId: Blockchain("eip155:1")!), context: nil), + (request: try! Request(topic: "invalidTopic1", method: "method2", params: AnyCodable("params2"), chainId: Blockchain("eip155:1")!), context: nil), + (request: try! Request(topic: "invalidTopic2", method: "method3", params: AnyCodable("params3"), chainId: Blockchain("eip155:1")!), context: nil) + ] + + sanitiser.removeInvalidSessionRequests(validSessionTopics: validSessionTopics) + + XCTAssertEqual(mockRPCHistory.deletedTopics.sorted(), ["invalidTopic1", "invalidTopic2"]) + } + + func testRemoveInvalidSessionRequests_withEmptyValidSessionTopics() { + let validSessionTopics: Set = [] + + mockHistoryService.pendingRequests = [ + (request: try! Request(topic: "invalidTopic1", method: "method1", params: AnyCodable("params1"), chainId: Blockchain("eip155:1")!), context: nil), + (request: try! Request(topic: "invalidTopic2", method: "method2", params: AnyCodable("params2"), chainId: Blockchain("eip155:1")!), context: nil) + ] + + sanitiser.removeInvalidSessionRequests(validSessionTopics: validSessionTopics) + + XCTAssertEqual(mockRPCHistory.deletedTopics.sorted(), ["invalidTopic1", "invalidTopic2"]) + } +} diff --git a/Tests/WalletConnectSignTests/SessionEngineTests.swift b/Tests/WalletConnectSignTests/SessionEngineTests.swift index e3a42232e..0f2828805 100644 --- a/Tests/WalletConnectSignTests/SessionEngineTests.swift +++ b/Tests/WalletConnectSignTests/SessionEngineTests.swift @@ -8,38 +8,35 @@ final class SessionEngineTests: XCTestCase { var networkingInteractor: NetworkingInteractorMock! var sessionStorage: WCSessionStorageMock! var verifyContextStore: CodableStore! + var rpcHistory: RPCHistory! var engine: SessionEngine! override func setUp() { networkingInteractor = NetworkingInteractorMock() sessionStorage = WCSessionStorageMock() + let defaults = RuntimeKeyValueStorage() + rpcHistory = RPCHistory( + keyValueStore: .init( + defaults: defaults, + identifier: "" + ) + ) verifyContextStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: "") + let historyService = HistoryService( + history: rpcHistory, + verifyContextStore: verifyContextStore + ) engine = SessionEngine( networkingInteractor: networkingInteractor, - historyService: HistoryService( - history: RPCHistory( - keyValueStore: .init( - defaults: RuntimeKeyValueStorage(), - identifier: "" - ) - ), - verifyContextStore: verifyContextStore - ), + historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: VerifyClientMock(), kms: KeyManagementServiceMock(), sessionStore: sessionStorage, logger: ConsoleLoggerMock(), sessionRequestsProvider: SessionRequestsProvider( - historyService: HistoryService( - history: RPCHistory( - keyValueStore: .init( - defaults: RuntimeKeyValueStorage(), - identifier: "" - ) - ), - verifyContextStore: verifyContextStore - )) + historyService: historyService), + invalidRequestsSanitiser: InvalidRequestsSanitiser(historyService: historyService, history: rpcHistory) ) } @@ -66,4 +63,46 @@ final class SessionEngineTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } + + func testRemovePendingRequestsOnSessionExpiration() { + let expectation = expectation( + description: "Remove pending requests on session expiration" + ) + + let historyService = MockHistoryService() + + engine = SessionEngine( + networkingInteractor: networkingInteractor, + historyService: historyService, + verifyContextStore: verifyContextStore, + verifyClient: VerifyClientMock(), + kms: KeyManagementServiceMock(), + sessionStore: sessionStorage, + logger: ConsoleLoggerMock(), + sessionRequestsProvider: SessionRequestsProvider( + historyService: historyService), + invalidRequestsSanitiser: InvalidRequestsSanitiser( + historyService: historyService, + history: rpcHistory + ) + ) + + let expectedTopic = "topic" + + let session = WCSession.stub( + topic: expectedTopic, + namespaces: SessionNamespace.stubDictionary() + ) + + sessionStorage.setSession(session) + + historyService.removePendingRequestCalled = { topic in + XCTAssertEqual(topic, expectedTopic) + expectation.fulfill() + } + + sessionStorage.onSessionExpiration!(session) + + wait(for: [expectation], timeout: 0.5) + } }