diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/EventsTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/EventsTests.xcscheme new file mode 100644 index 000000000..7f7753025 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/EventsTests.xcscheme @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/DApp/SceneDelegate.swift b/Example/DApp/SceneDelegate.swift index 2153d26f3..5d5c3cb62 100644 --- a/Example/DApp/SceneDelegate.swift +++ b/Example/DApp/SceneDelegate.swift @@ -117,6 +117,15 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { order: 1, mobileLink: "walletapp://", linkMode: "https://lab.web3modal.com/wallet" + ), + .init( + id: "rn-sample", + name: "RN Sample Wallet", + homepage: "https://walletconnect.com/", + imageUrl: "https://avatars.githubusercontent.com/u/37784886?s=200&v=4", + order: 1, + mobileLink: "rn-web3wallet://", + linkMode: "https://lab.web3modal.com/walletkit_rn" ) ] ) diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 2ea13ae0e..80c9e5f0a 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -42,7 +42,8 @@ final class PairingTests: XCTestCase { logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychain, - networkingClient: networkingClient) + networkingClient: networkingClient, + eventsClient: MockEventsClient()) return pairingClient diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 56262453f..31f09a9e3 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -48,7 +48,8 @@ final class SignClientTests: XCTestCase { logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychain, - networkingClient: networkingClient + networkingClient: networkingClient, + eventsClient: MockEventsClient() ) let metadata = AppMetadata(name: name, description: "", url: "", icons: [""], redirect: try! AppMetadata.Redirect(native: "", universal: linkModeUniversalLink, linkMode: supportLinkMode)) @@ -61,7 +62,8 @@ final class SignClientTests: XCTestCase { networkingClient: networkingClient, iatProvider: IATProviderMock(), projectId: InputConfig.projectId, - crypto: DefaultCryptoProvider() + crypto: DefaultCryptoProvider(), + eventsClient: MockEventsClient() ) let clientId = try! networkingClient.getClientId() diff --git a/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift b/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift index 8420a3a64..5826e1f1a 100644 --- a/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift +++ b/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift @@ -46,7 +46,8 @@ final class XPlatformW3WTests: XCTestCase { logger: pairingLogger, keyValueStorage: keyValueStorage, keychainStorage: keychain, - networkingClient: networkingClient) + networkingClient: networkingClient, + eventsClient: MockEventsClient()) let signClient = SignClientFactory.create( metadata: AppMetadata(name: name, description: "", url: "", icons: [""], redirect: try! AppMetadata.Redirect(native: "", universal: nil)), @@ -57,7 +58,8 @@ final class XPlatformW3WTests: XCTestCase { networkingClient: networkingClient, iatProvider: DefaultIATProvider(), projectId: InputConfig.projectId, - crypto: DefaultCryptoProvider() + crypto: DefaultCryptoProvider(), + eventsClient: MockEventsClient() ) w3wClient = Web3WalletClientFactory.create( diff --git a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift index eef6758de..e3888e786 100644 --- a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift +++ b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift @@ -52,9 +52,7 @@ final class RelayClientEndToEndTests: XCTestCase { socketAuthenticator: socketAuthenticator ) - let socketUrlFallbackHandler = SocketUrlFallbackHandler(relayUrlFactory: relayUrlFactory, logger: logger, socket: socket, networkMonitor: networkMonitor) - - let socketConnectionHandler = AutomaticSocketConnectionHandler(socket: socket, logger: logger, socketUrlFallbackHandler: socketUrlFallbackHandler) + let socketConnectionHandler = AutomaticSocketConnectionHandler(socket: socket, logger: logger) let dispatcher = Dispatcher( socketFactory: webSocketFactory, relayUrlFactory: urlFactory, diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 2eda07ec7..970353746 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -33,6 +33,7 @@ final class ConfigurationService { Notify.instance.setLogging(level: .debug) Sign.instance.setLogging(level: .debug) + Events.instance.setLogging(level: .debug) if let clientId = try? Networking.interactor.getClientId() { LoggingService.instance.setUpUser(account: importAccount.account.absoluteString, clientId: clientId) diff --git a/Package.swift b/Package.swift index b255c6b99..7e5c3133c 100644 --- a/Package.swift +++ b/Package.swift @@ -51,7 +51,7 @@ let package = Package( targets: [ .target( name: "WalletConnectSign", - dependencies: ["WalletConnectPairing", "WalletConnectVerify", "WalletConnectSigner"], + dependencies: ["WalletConnectPairing", "WalletConnectVerify", "WalletConnectSigner", "Events"], path: "Sources/WalletConnectSign", resources: [.process("Resources/PrivacyInfo.xcprivacy")]), .target( @@ -84,7 +84,7 @@ let package = Package( path: "Sources/WalletConnectKMS"), .target( name: "WalletConnectPairing", - dependencies: ["WalletConnectNetworking"], + dependencies: ["WalletConnectNetworking", "Events"], resources: [.process("Resources/PrivacyInfo.xcprivacy")]), .target( name: "WalletConnectSigner", @@ -126,6 +126,9 @@ let package = Package( .target( name: "Database", dependencies: ["WalletConnectUtils"]), + .target( + name: "Events", + dependencies: ["WalletConnectUtils", "WalletConnectNetworking"]), .target( name: "WalletConnectModal", dependencies: ["QRCode", "WalletConnectSign"], @@ -169,7 +172,10 @@ let package = Package( dependencies: ["Commons", "TestingUtils"]), .testTarget( name: "WalletConnectModalTests", - dependencies: ["WalletConnectModal", "TestingUtils"]) + dependencies: ["WalletConnectModal", "TestingUtils"]), + .testTarget( + name: "EventsTests", + dependencies: ["Events"]), ], swiftLanguageVersions: [.v5] ) diff --git a/Sources/Events/Event.swift b/Sources/Events/Event.swift new file mode 100644 index 000000000..cc8c4d3c1 --- /dev/null +++ b/Sources/Events/Event.swift @@ -0,0 +1,19 @@ +import Foundation + +struct Event: Codable { + let eventId: String + let bundleId: String + let timestamp: Int64 + let props: Props +} + +struct Props: Codable { + let event: String + let type: String + let properties: Properties? +} + +struct Properties: Codable { + let topic: String? + let trace: [String]? +} diff --git a/Sources/Events/EventStorage.swift b/Sources/Events/EventStorage.swift new file mode 100644 index 000000000..691ef903e --- /dev/null +++ b/Sources/Events/EventStorage.swift @@ -0,0 +1,57 @@ + +import Foundation + +protocol EventStorage { + func saveErrorEvent(_ event: Event) + func fetchErrorEvents() -> [Event] + func clearErrorEvents() +} + +class UserDefaultsEventStorage: EventStorage { + private let errorEventsKey = "com.walletconnect.sdk.errorEvents" + private let maxEvents = 30 + + func saveErrorEvent(_ event: Event) { + var existingEvents = fetchErrorEvents() + existingEvents.append(event) + // Ensure we keep only the last 30 events + if existingEvents.count > maxEvents { + existingEvents = Array(existingEvents.suffix(maxEvents)) + } + if let encoded = try? JSONEncoder().encode(existingEvents) { + UserDefaults.standard.set(encoded, forKey: errorEventsKey) + } + } + + func fetchErrorEvents() -> [Event] { + if let data = UserDefaults.standard.data(forKey: errorEventsKey), + let events = try? JSONDecoder().decode([Event].self, from: data) { + // Return only the last 30 events + return Array(events.suffix(maxEvents)) + } + return [] + } + + func clearErrorEvents() { + UserDefaults.standard.removeObject(forKey: errorEventsKey) + } +} + +#if DEBUG +class MockEventStorage: EventStorage { + private(set) var savedEvents: [Event] = [] + + func saveErrorEvent(_ event: Event) { + savedEvents.append(event) + } + + func fetchErrorEvents() -> [Event] { + return savedEvents + } + + func clearErrorEvents() { + savedEvents.removeAll() + } +} +#endif + diff --git a/Sources/Events/Events.swift b/Sources/Events/Events.swift new file mode 100644 index 000000000..5b9d3e57a --- /dev/null +++ b/Sources/Events/Events.swift @@ -0,0 +1,11 @@ +import Foundation + +public class Events { + /// Singleton instance of EventsClient + public static var instance: EventsClient = { + return EventsClientFactory.create( + projectId: Networking.projectId, + sdkVersion: EnvironmentInfo.sdkName + ) + }() +} diff --git a/Sources/Events/EventsClient.swift b/Sources/Events/EventsClient.swift new file mode 100644 index 000000000..f0bcb32c3 --- /dev/null +++ b/Sources/Events/EventsClient.swift @@ -0,0 +1,107 @@ +import Foundation + +public protocol EventsClientProtocol { + func startTrace(topic: String) + func saveEvent(_ event: TraceEvent) + func setTopic(_ topic: String) + func setTelemetryEnabled(_ enabled: Bool) +} + +public class EventsClient: EventsClientProtocol { + private let eventsCollector: EventsCollector + private let eventsDispatcher: EventsDispatcher + private let logger: ConsoleLogging + private var stateStorage: TelemetryStateStorage + + init( + eventsCollector: EventsCollector, + eventsDispatcher: EventsDispatcher, + logger: ConsoleLogging, + stateStorage: TelemetryStateStorage + ) { + self.eventsCollector = eventsCollector + self.eventsDispatcher = eventsDispatcher + self.logger = logger + self.stateStorage = stateStorage + + if stateStorage.telemetryEnabled { + Task { await sendStoredEvents() } + } else { + self.eventsCollector.storage.clearErrorEvents() + } + } + + public func setLogging(level: LoggingLevel) { + logger.setLogging(level: level) + } + + // Public method to start trace + public func startTrace(topic: String) { + guard stateStorage.telemetryEnabled else { return } + logger.debug("Will start trace with topic: \(topic)") + eventsCollector.startTrace(topic: topic) + } + + public func setTopic(_ topic: String) { + guard stateStorage.telemetryEnabled else { return } + eventsCollector.setTopic(topic) + } + + // Public method to save event + public func saveEvent(_ event: TraceEvent) { + guard stateStorage.telemetryEnabled else { return } + logger.debug("Will store an event: \(event)") + eventsCollector.saveEvent(event) + } + + // Public method to set telemetry enabled or disabled + public func setTelemetryEnabled(_ enabled: Bool) { + stateStorage.telemetryEnabled = enabled + if enabled { + Task { await sendStoredEvents() } + } else { + eventsCollector.storage.clearErrorEvents() + } + } + + private func sendStoredEvents() async { + guard stateStorage.telemetryEnabled else { return } + let events = eventsCollector.storage.fetchErrorEvents() + guard !events.isEmpty else { return } + + logger.debug("Will send events") + do { + let success: Bool = try await eventsDispatcher.executeWithRetry(events: events) + if success { + logger.debug("Events sent successfully") + self.eventsCollector.storage.clearErrorEvents() + } + } catch { + logger.debug("Failed to send events after multiple attempts: \(error)") + } + } +} + +#if DEBUG +public class MockEventsClient: EventsClientProtocol { + var startTraceCalled = false + var saveEventCalled = false + var telemetryEnabled = true + + public init() {} + + public func startTrace(topic: String) { + startTraceCalled = true + } + + public func setTopic(_ topic: String) {} + + public func saveEvent(_ event: TraceEvent) { + saveEventCalled = true + } + + public func setTelemetryEnabled(_ enabled: Bool) { + telemetryEnabled = enabled + } +} +#endif diff --git a/Sources/Events/EventsClientFactory.swift b/Sources/Events/EventsClientFactory.swift new file mode 100644 index 000000000..43bff4c76 --- /dev/null +++ b/Sources/Events/EventsClientFactory.swift @@ -0,0 +1,25 @@ +import Foundation + +public class EventsClientFactory { + static func create( + projectId: String, + sdkVersion: String, + storage: EventStorage = UserDefaultsEventStorage() + ) -> EventsClient { + let networkingService = NetworkingService( + projectId: projectId, + sdkVersion: sdkVersion + ) + let logger = ConsoleLogger(prefix: "🧚🏻‍♂️", loggingLevel: .off) + let retryPolicy = RetryPolicy(maxAttempts: 3, initialDelay: 5, multiplier: 2) + let eventsDispatcher = EventsDispatcher(networkingService: networkingService, retryPolicy: retryPolicy) + let eventsCollector = EventsCollector(storage: storage, logger: logger) + return EventsClient( + eventsCollector: eventsCollector, + eventsDispatcher: eventsDispatcher, + logger: logger, + stateStorage: UserDefaultsTelemetryStateStorage() + ) + } +} + diff --git a/Sources/Events/EventsCollector.swift b/Sources/Events/EventsCollector.swift new file mode 100644 index 000000000..2ace98002 --- /dev/null +++ b/Sources/Events/EventsCollector.swift @@ -0,0 +1,71 @@ +import Foundation + +// Protocol for TraceEvent +public protocol TraceEvent: CustomStringConvertible { + var description: String { get } +} + +// Protocol for ErrorEvent +protocol ErrorEvent: TraceEvent {} + + +class EventsCollector { + var trace: [String] = [] + var topic: String? + let storage: EventStorage + private let logger: ConsoleLogging + + init( + storage: EventStorage, + logger: ConsoleLogging + ) { + self.storage = storage + self.logger = logger + } + + // Function to start trace with topic + func startTrace(topic: String) { + self.topic = topic + self.trace = [] + } + + func setTopic(_ topic: String) { + self.topic = topic + } + + // Function to save event + func saveEvent(_ event: TraceEvent) { + trace.append(event.description) + if let errorEvent = event as? ErrorEvent { + saveErrorEvent(errorEvent) + endTrace() + } + } + + // Function to end trace + private func endTrace() { + self.topic = nil + self.trace = [] + } + + // Private function to save error event + private func saveErrorEvent(_ errorEvent: ErrorEvent) { + let bundleId = Bundle.main.bundleIdentifier ?? "Unknown" + let event = Event( + eventId: UUID().uuidString, + bundleId: bundleId, + timestamp: Int64(Date().timeIntervalSince1970 * 1000), + props: Props( + event: "ERROR", + type: errorEvent.description, + properties: Properties( + topic: topic, + trace: trace + ) + ) + ) + storage.saveErrorEvent(event) + logger.debug("Error event saved: \(event)") + } +} + diff --git a/Sources/Events/EventsDispatcher.swift b/Sources/Events/EventsDispatcher.swift new file mode 100644 index 000000000..0c347958b --- /dev/null +++ b/Sources/Events/EventsDispatcher.swift @@ -0,0 +1,42 @@ + +import Foundation + +struct RetryPolicy { + let maxAttempts: Int + let initialDelay: TimeInterval + let multiplier: Double + var delayOverride: TimeInterval? = nil +} + +class EventsDispatcher { + private let networkingService: NetworkingServiceProtocol + private let retryPolicy: RetryPolicy + + init(networkingService: NetworkingServiceProtocol, retryPolicy: RetryPolicy) { + self.networkingService = networkingService + self.retryPolicy = retryPolicy + } + + func executeWithRetry(events: [Event]) async throws -> Bool { + var attempts = 0 + var delay = retryPolicy.initialDelay + + while attempts < retryPolicy.maxAttempts { + if attempts > 0 || retryPolicy.initialDelay > 0 { + let actualDelay = retryPolicy.delayOverride ?? delay + try await Task.sleep(nanoseconds: UInt64(actualDelay * Double(NSEC_PER_SEC))) + delay *= retryPolicy.multiplier + } + + do { + return try await networkingService.sendEvents(events) + } catch { + attempts += 1 + if attempts >= retryPolicy.maxAttempts { + throw error + } + } + } + throw NSError(domain: "EventsDispatcherError", code: -1, userInfo: [NSLocalizedDescriptionKey: "Max retry attempts reached"]) + } +} diff --git a/Sources/Events/EventsImports.swift b/Sources/Events/EventsImports.swift new file mode 100644 index 000000000..5e63a68cb --- /dev/null +++ b/Sources/Events/EventsImports.swift @@ -0,0 +1,5 @@ +#if !CocoaPods +@_exported import WalletConnectUtils +@_exported import WalletConnectNetworking +@_exported import WalletConnectRelay +#endif diff --git a/Sources/Events/ExecutionTraces/PairingExecutionTraceEvents.swift b/Sources/Events/ExecutionTraces/PairingExecutionTraceEvents.swift new file mode 100644 index 000000000..bfb809dd3 --- /dev/null +++ b/Sources/Events/ExecutionTraces/PairingExecutionTraceEvents.swift @@ -0,0 +1,31 @@ + +import Foundation + +public enum PairingExecutionTraceEvents: String, TraceEvent { + case pairingUriValidationSuccess = "pairing_uri_validation_success" + case pairingStarted = "pairing_started" + case noWssConnection = "no_wss_connection" + case storeNewPairing = "store_new_pairing" + case subscribingPairingTopic = "subscribing_pairing_topic" + case subscribePairingTopicSuccess = "subscribe_pairing_topic_success" + case pairingHasPendingRequest = "pairing_has_pending_request" + case emitSessionProposal = "emit_session_proposal" + + public var description: String { + return self.rawValue + } +} + +// Enum for TraceErrorEvents +public enum PairingTraceErrorEvents: String, ErrorEvent { + case noInternetConnection = "no_internet_connection" + case malformedPairingUri = "malformed_pairing_uri" + case activePairingAlreadyExists = "active_pairing_already_exists" + case subscribePairingTopicFailure = "subscribe_pairing_topic_failure" + case pairingExpired = "pairing_expired" + case proposalExpired = "proposal_expired" + + public var description: String { + return self.rawValue + } +} diff --git a/Sources/Events/ExecutionTraces/SessionApproveExecutionTraceEvents.swift b/Sources/Events/ExecutionTraces/SessionApproveExecutionTraceEvents.swift new file mode 100644 index 000000000..95cc48ca6 --- /dev/null +++ b/Sources/Events/ExecutionTraces/SessionApproveExecutionTraceEvents.swift @@ -0,0 +1,29 @@ + +import Foundation + +public enum SessionApproveExecutionTraceEvents: String, TraceEvent { + case approvingSessionProposal = "approving_session_proposal" + case sessionNamespacesValidationStarted = "session_namespaces_validation_started" + case sessionNamespacesValidationSuccess = "session_namespaces_validation_success" + case responseApproveSent = "response_approve_sent" + case settleRequestSent = "settle_request_sent" + case sessionSettleSuccess = "session_settle_success" + + public var description: String { + return self.rawValue + } +} + +public enum ApproveSessionTraceErrorEvents: String, ErrorEvent { + case sessionNamespacesValidationFailure = "session_namespaces_validation_failure" + case proposalNotFound = "proposal_not_found" + case proposalExpired = "proposal_expired" + case networkNotConnected = "network_not_connected" + case agreementMissingOrInvalid = "agreement_missing_or_invalid" + case relayNotFound = "relay_not_found" + case sessionSettleFailure = "session_settle_failure" + + public var description: String { + return self.rawValue + } +} diff --git a/Sources/Events/ExecutionTraces/SessionAuthenticateTraceEvents.swift b/Sources/Events/ExecutionTraces/SessionAuthenticateTraceEvents.swift new file mode 100644 index 000000000..88b2f0696 --- /dev/null +++ b/Sources/Events/ExecutionTraces/SessionAuthenticateTraceEvents.swift @@ -0,0 +1,33 @@ + +import Foundation + +public enum SessionAuthenticateTraceEvents: String, TraceEvent { + case signatureVerificationStarted = "signature_verification_started" + case signatureVerificationSuccess = "signature_verification_success" + case requestParamsRetrieved = "request_params_retrieved" + case agreementKeysGenerated = "agreement_keys_generated" + case agreementSecretSet = "agreement_secret_set" + case sessionKeysGenerated = "session_keys_generated" + case sessionSecretSet = "session_secret_set" + case responseParamsCreated = "response_params_created" + case responseSent = "response_sent" + + public var description: String { + return self.rawValue + } +} + +public enum SessionAuthenticateErrorEvents: String, ErrorEvent { + case signatureVerificationFailed = "signature_verification_failed" + case requestParamsRetrievalFailed = "request_params_retrieval_failed" + case agreementKeysGenerationFailed = "agreement_keys_generation_failed" + case agreementSecretSetFailed = "agreement_secret_set_failed" + case sessionKeysGenerationFailed = "session_keys_generation_failed" + case sessionSecretSetFailed = "session_secret_set_failed" + case sessionCreationFailed = "session_creation_failed" + case responseSendFailed = "response_send_failed" + + public var description: String { + return self.rawValue + } +} diff --git a/Sources/Events/NetworkingService.swift b/Sources/Events/NetworkingService.swift new file mode 100644 index 000000000..a8a03b620 --- /dev/null +++ b/Sources/Events/NetworkingService.swift @@ -0,0 +1,62 @@ +import Foundation + +protocol NetworkingServiceProtocol { + func sendEvents(_ events: [Event]) async throws -> Bool +} + +class NetworkingService: NetworkingServiceProtocol { + private let session: URLSession + private let projectId: String + private let sdkVersion: String + private let apiURL = URL(string: "https://pulse.walletconnect.com/batch")! + + init(session: URLSession = .shared, projectId: String, sdkVersion: String) { + self.session = session + self.projectId = projectId + self.sdkVersion = sdkVersion + } + + func sendEvents(_ events: [Event]) async throws -> Bool { + var request = URLRequest(url: apiURL) + request.httpMethod = "POST" + request.setValue("application/json", forHTTPHeaderField: "Content-Type") + request.setValue(projectId, forHTTPHeaderField: "x-project-id") + request.setValue("events_sdk", forHTTPHeaderField: "x-sdk-type") + request.setValue(sdkVersion, forHTTPHeaderField: "x-sdk-version") + + request.httpBody = try JSONEncoder().encode(events) + + return try await withCheckedThrowingContinuation { continuation in + let task = session.dataTask(with: request) { data, response, error in + if let error = error { + continuation.resume(throwing: error) + return + } + + if let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) { + continuation.resume(returning: true) + } else { + continuation.resume(returning: false) + } + } + + task.priority = URLSessionTask.lowPriority + task.resume() + } + } +} + +#if DEBUG +class MockNetworkingService: NetworkingServiceProtocol { + var shouldFail = false + var attemptCount = 0 + + func sendEvents(_ events: [Event]) async throws -> Bool { + attemptCount += 1 + if shouldFail { + throw NSError(domain: "MockError", code: -1, userInfo: nil) + } + return true + } +} +#endif diff --git a/Sources/Events/UserDefaultsStateStorage.swift b/Sources/Events/UserDefaultsStateStorage.swift new file mode 100644 index 000000000..fa00eee21 --- /dev/null +++ b/Sources/Events/UserDefaultsStateStorage.swift @@ -0,0 +1,54 @@ +import Foundation + +protocol TelemetryStateStorage { + var telemetryEnabled: Bool { get set } +} + +class UserDefaultsTelemetryStateStorage: TelemetryStateStorage { + private let telemetryEnabledKey = "com.walletconnect.sdk.telemetryEnabled" + + var telemetryEnabled: Bool { + get { + if UserDefaults.standard.object(forKey: telemetryEnabledKey) == nil { + return true + } + return UserDefaults.standard.bool(forKey: telemetryEnabledKey) + } + set { + UserDefaults.standard.set(newValue, forKey: telemetryEnabledKey) + } + } + + init() { + if UserDefaults.standard.object(forKey: telemetryEnabledKey) == nil { + // Set default value if not already set + UserDefaults.standard.set(true, forKey: telemetryEnabledKey) + } + } +} + +#if DEBUG +class MockUserDefaultsTelemetryStateStorage: TelemetryStateStorage { + private var mockStorage: [String: Any] = [:] + private let telemetryEnabledKey = "com.walletconnect.sdk.telemetryEnabled" + + var telemetryEnabled: Bool { + get { + if mockStorage[telemetryEnabledKey] == nil { + return true + } + return mockStorage[telemetryEnabledKey] as? Bool ?? true + } + set { + mockStorage[telemetryEnabledKey] = newValue + } + } + + init() { + // Initialize with a default value if not already set + if mockStorage[telemetryEnabledKey] == nil { + mockStorage[telemetryEnabledKey] = true + } + } +} +#endif diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index a79814399..e916c3f62 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -2,6 +2,7 @@ import Foundation import Combine public protocol NetworkInteracting { + var isSocketConnected: Bool { get } var socketConnectionStatusPublisher: AnyPublisher { get } var networkConnectionStatusPublisher: AnyPublisher { get } var requestPublisher: AnyPublisher<(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?), Never> { get } diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 847606edd..e7c8eb90d 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -27,6 +27,10 @@ public class NetworkingInteractor: NetworkInteracting { .eraseToAnyPublisher() } + public var isSocketConnected: Bool { + return relayClient.isSocketConnected + } + public var networkConnectionStatusPublisher: AnyPublisher public var socketConnectionStatusPublisher: AnyPublisher diff --git a/Sources/WalletConnectPairing/Pair.swift b/Sources/WalletConnectPairing/Pair.swift index 08eafec7e..8924420cd 100644 --- a/Sources/WalletConnectPairing/Pair.swift +++ b/Sources/WalletConnectPairing/Pair.swift @@ -37,6 +37,10 @@ private extension Pair { guard let config = Pair.config else { fatalError("Error - you must call Pair.configure(_:) before accessing the shared instance.") } - return PairingClientFactory.create(networkingClient: Networking.interactor, groupIdentifier: Networking.groupIdentifier) + return PairingClientFactory.create( + networkingClient: Networking.interactor, + eventsClient: Events.instance, + groupIdentifier: Networking.groupIdentifier + ) }() } diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift index 22840bed3..26293c3ad 100644 --- a/Sources/WalletConnectPairing/PairingClientFactory.swift +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -4,6 +4,7 @@ public struct PairingClientFactory { public static func create( networkingClient: NetworkingInteractor, + eventsClient: EventsClient, groupIdentifier: String ) -> PairingClient { let logger = ConsoleLogger(loggingLevel: .off) @@ -13,20 +14,21 @@ public struct PairingClientFactory { } let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk", accessGroup: groupIdentifier) - return PairingClientFactory.create(logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, networkingClient: networkingClient) + return PairingClientFactory.create(logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, networkingClient: networkingClient, eventsClient: eventsClient) } public static func create( logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, - networkingClient: NetworkingInteractor + networkingClient: NetworkingInteractor, + eventsClient: EventsClientProtocol ) -> PairingClient { let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: PairStorageIdentifiers.pairings.rawValue))) let kms = KeyManagementService(keychain: keychainStorage) let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) let appPairService = AppPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore) - let walletPairService = WalletPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, history: history, logger: logger) + let walletPairService = WalletPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, history: history, logger: logger, eventsClient: eventsClient) let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingClient, pairingStorage: pairingStore, logger: logger) let pairingsProvider = PairingsProvider(pairingStorage: pairingStore) let cleanupService = PairingCleanupService(pairingStore: pairingStore, kms: kms) diff --git a/Sources/WalletConnectPairing/PairingImports.swift b/Sources/WalletConnectPairing/PairingImports.swift index 23c1738ef..98b75f8a5 100644 --- a/Sources/WalletConnectPairing/PairingImports.swift +++ b/Sources/WalletConnectPairing/PairingImports.swift @@ -1,3 +1,4 @@ #if !CocoaPods @_exported import WalletConnectNetworking +@_exported import Events #endif diff --git a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift index 2592d010d..c5607aa59 100644 --- a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift +++ b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift @@ -8,6 +8,7 @@ actor WalletPairService { let networkingInteractor: NetworkInteracting let kms: KeyManagementServiceProtocol + private let eventsClient: EventsClientProtocol private let pairingStorage: WCPairingStorage private let history: RPCHistory private let logger: ConsoleLogging @@ -17,34 +18,50 @@ actor WalletPairService { kms: KeyManagementServiceProtocol, pairingStorage: WCPairingStorage, history: RPCHistory, - logger: ConsoleLogging + logger: ConsoleLogging, + eventsClient: EventsClientProtocol ) { self.networkingInteractor = networkingInteractor self.kms = kms self.pairingStorage = pairingStorage self.history = history self.logger = logger + self.eventsClient = eventsClient } func pair(_ uri: WalletConnectURI) async throws { + eventsClient.startTrace(topic: uri.topic) + eventsClient.saveEvent(PairingExecutionTraceEvents.pairingStarted) logger.debug("Pairing with uri: \(uri)") guard try !pairingHasPendingRequest(for: uri.topic) else { + eventsClient.saveEvent(PairingExecutionTraceEvents.pairingHasPendingRequest) logger.debug("Pairing with topic (\(uri.topic)) has pending request") return } - + if !networkingInteractor.isSocketConnected { + eventsClient.saveEvent(PairingExecutionTraceEvents.noWssConnection) + } + let pairing = WCPairing(uri: uri) let symKey = try SymmetricKey(hex: uri.symKey) try kms.setSymmetricKey(symKey, for: pairing.topic) pairingStorage.setPairing(pairing) - + eventsClient.saveEvent(PairingExecutionTraceEvents.storeNewPairing) + let networkConnectionStatus = await resolveNetworkConnectionStatus() guard networkConnectionStatus == .connected else { logger.debug("Pairing failed - Network is not connected") + eventsClient.saveEvent(PairingTraceErrorEvents.noInternetConnection) throw Errors.networkNotConnected } - - try await networkingInteractor.subscribe(topic: pairing.topic) + eventsClient.saveEvent(PairingExecutionTraceEvents.subscribingPairingTopic) + do { + try await networkingInteractor.subscribe(topic: pairing.topic) + } catch { + logger.debug("Failed to subscribe to topic: \(pairing.topic)") + eventsClient.saveEvent(PairingTraceErrorEvents.subscribePairingTopicFailure) + throw error + } } } @@ -56,6 +73,7 @@ extension WalletPairService { } if pairing.active { + eventsClient.saveEvent(PairingTraceErrorEvents.activePairingAlreadyExists) throw Errors.pairingAlreadyExist(topic: topic) } @@ -67,6 +85,7 @@ extension WalletPairService { guard !pendingRequests.isEmpty else { return false } pendingRequests.forEach { request in + eventsClient.saveEvent(PairingExecutionTraceEvents.emitSessionProposal) networkingInteractor.handleHistoryRequest(topic: topic, request: request) } return true diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift index 81ede05d7..3af72ce97 100644 --- a/Sources/WalletConnectRelay/Dispatching.swift +++ b/Sources/WalletConnectRelay/Dispatching.swift @@ -3,6 +3,7 @@ import Combine protocol Dispatching { var onMessage: ((String) -> Void)? { get set } + var isSocketConnected: Bool { get } var networkConnectionStatusPublisher: AnyPublisher { get } var socketConnectionStatusPublisher: AnyPublisher { get } func send(_ string: String, completion: @escaping (Error?) -> Void) @@ -32,6 +33,10 @@ final class Dispatcher: NSObject, Dispatching { networkMonitor.networkConnectionStatusPublisher } + var isSocketConnected: Bool { + return networkMonitor.isConnected + } + private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.dispatcher", qos: .utility, attributes: .concurrent) init( diff --git a/Sources/WalletConnectRelay/Misc/NetworkConstants.swift b/Sources/WalletConnectRelay/Misc/NetworkConstants.swift index 5a23a21c8..9f2d6f259 100644 --- a/Sources/WalletConnectRelay/Misc/NetworkConstants.swift +++ b/Sources/WalletConnectRelay/Misc/NetworkConstants.swift @@ -1,4 +1,3 @@ enum NetworkConstants { - static var defaultUrl = "relay.walletconnect.com" - static var fallbackUrl = "relay.walletconnect.org" + static var defaultUrl = "relay.walletconnect.org" } diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index ec58e3328..c09ac47c5 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.19.4"} +{"version": "1.19.5"} diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index efa501499..93b33f1c7 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -22,6 +22,10 @@ public final class RelayClient { var subscriptions: [String: String] = [:] + public var isSocketConnected: Bool { + return dispatcher.isSocketConnected + } + public var messagePublisher: AnyPublisher<(topic: String, message: String, publishedAt: Date), Never> { messagePublisherSubject.eraseToAnyPublisher() } diff --git a/Sources/WalletConnectRelay/RelayClientFactory.swift b/Sources/WalletConnectRelay/RelayClientFactory.swift index 3a4097336..2120b720e 100644 --- a/Sources/WalletConnectRelay/RelayClientFactory.swift +++ b/Sources/WalletConnectRelay/RelayClientFactory.swift @@ -61,16 +61,11 @@ public struct RelayClientFactory { if let bundleId = Bundle.main.bundleIdentifier { socket.request.addValue(bundleId, forHTTPHeaderField: "Origin") } - let socketFallbackHandler = SocketUrlFallbackHandler( - relayUrlFactory: relayUrlFactory, - logger: logger, - socket: socket, - networkMonitor: networkMonitor - ) + var socketConnectionHandler: SocketConnectionHandler! switch socketConnectionType { - case .automatic: socketConnectionHandler = AutomaticSocketConnectionHandler(socket: socket, logger: logger, socketUrlFallbackHandler: socketFallbackHandler) - case .manual: socketConnectionHandler = ManualSocketConnectionHandler(socket: socket, logger: logger, socketUrlFallbackHandler: socketFallbackHandler) + case .automatic: socketConnectionHandler = AutomaticSocketConnectionHandler(socket: socket, logger: logger) + case .manual: socketConnectionHandler = ManualSocketConnectionHandler(socket: socket, logger: logger) } let dispatcher = Dispatcher( diff --git a/Sources/WalletConnectRelay/RelayURLFactory.swift b/Sources/WalletConnectRelay/RelayURLFactory.swift index 20290fd24..5809c89fa 100644 --- a/Sources/WalletConnectRelay/RelayURLFactory.swift +++ b/Sources/WalletConnectRelay/RelayURLFactory.swift @@ -18,19 +18,15 @@ class RelayUrlFactory { self.socketAuthenticator = socketAuthenticator } - func setFallback() { - self.fallback = true - } - func create() -> URL { var components = URLComponents() components.scheme = "wss" - components.host = fallback ? NetworkConstants.fallbackUrl : relayHost + components.host = relayHost components.queryItems = [ URLQueryItem(name: "projectId", value: projectId) ] do { - let authToken = try socketAuthenticator.createAuthToken(url: fallback ? "wss://" + NetworkConstants.fallbackUrl : "wss://" + relayHost) + let authToken = try socketAuthenticator.createAuthToken(url: "wss://" + relayHost) components.queryItems?.append(URLQueryItem(name: "auth", value: authToken)) } catch { // TODO: Handle token creation errors diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift index 3be94af13..c9ea12219 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift @@ -14,9 +14,8 @@ class AutomaticSocketConnectionHandler { private let appStateObserver: AppStateObserving private let networkMonitor: NetworkMonitoring private let backgroundTaskRegistrar: BackgroundTaskRegistering - private let defaultTimeout: Int = 5 + private let defaultTimeout: Int = 60 private let logger: ConsoleLogging - private var socketUrlFallbackHandler: SocketUrlFallbackHandler private var publishers = Set() private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.automatic_socket_connection", qos: .utility, attributes: .concurrent) @@ -26,25 +25,17 @@ class AutomaticSocketConnectionHandler { networkMonitor: NetworkMonitoring = NetworkMonitor(), appStateObserver: AppStateObserving = AppStateObserver(), backgroundTaskRegistrar: BackgroundTaskRegistering = BackgroundTaskRegistrar(), - logger: ConsoleLogging, - socketUrlFallbackHandler: SocketUrlFallbackHandler + logger: ConsoleLogging ) { self.appStateObserver = appStateObserver self.socket = socket self.networkMonitor = networkMonitor self.backgroundTaskRegistrar = backgroundTaskRegistrar self.logger = logger - self.socketUrlFallbackHandler = socketUrlFallbackHandler setUpStateObserving() setUpNetworkMonitoring() - socketUrlFallbackHandler.onTryReconnect = { [unowned self] in - Task(priority: .high) { - await tryReconect() - } - } - connect() } @@ -62,8 +53,8 @@ class AutomaticSocketConnectionHandler { return } if !self.socket.isConnected { - self.logger.debug("Connection timed out, initiating fallback...") - self.socketUrlFallbackHandler.handleFallbackIfNeeded(error: .connectionFailed) + self.logger.debug("Connection timed out, will rety to connect...") + retryToConnect() } timer.cancel() } @@ -99,6 +90,12 @@ class AutomaticSocketConnectionHandler { socket.disconnect() } + private func retryToConnect() { + if !socket.isConnected { + connect() + } + } + private func reconnectIfNeeded() { if !socket.isConnected { socket.connect() @@ -109,11 +106,6 @@ class AutomaticSocketConnectionHandler { // MARK: - SocketConnectionHandler extension AutomaticSocketConnectionHandler: SocketConnectionHandler { - func tryReconect() async { - guard await appStateObserver.currentState == .foreground else { return } - reconnectIfNeeded() - } - func handleConnect() throws { throw Errors.manualSocketConnectionForbidden } diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift index d0589ca9e..04152bd21 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift @@ -4,23 +4,14 @@ class ManualSocketConnectionHandler: SocketConnectionHandler { private let socket: WebSocketConnecting private let logger: ConsoleLogging - private let defaultTimeout: Int = 5 - private var socketUrlFallbackHandler: SocketUrlFallbackHandler + private let defaultTimeout: Int = 60 private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.manual_socket_connection", attributes: .concurrent) init( socket: WebSocketConnecting, - logger: ConsoleLogging, - socketUrlFallbackHandler: SocketUrlFallbackHandler) { + logger: ConsoleLogging) { self.socket = socket self.logger = logger - self.socketUrlFallbackHandler = socketUrlFallbackHandler - - socketUrlFallbackHandler.onTryReconnect = { [unowned self] in - Task(priority: .high) { - await tryReconect() - } - } } func handleConnect() throws { @@ -34,8 +25,8 @@ class ManualSocketConnectionHandler: SocketConnectionHandler { return } if !self.socket.isConnected { - self.logger.debug("Connection timed out, initiating fallback...") - self.socketUrlFallbackHandler.handleFallbackIfNeeded(error: .connectionFailed) + self.logger.debug("Connection timed out, will rety to connect...") + retryToConnect() } timer.cancel() } @@ -51,7 +42,7 @@ class ManualSocketConnectionHandler: SocketConnectionHandler { // ManualSocketConnectionHandler does not support reconnection logic } - func tryReconect() async { + private func retryToConnect() { if !socket.isConnected { socket.connect() } diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift index 91284893b..4ac3046dd 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift @@ -4,5 +4,4 @@ protocol SocketConnectionHandler { func handleConnect() throws func handleDisconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws func handleDisconnection() async - func tryReconect() async } diff --git a/Sources/WalletConnectRelay/SocketUrlFallbackHandler.swift b/Sources/WalletConnectRelay/SocketUrlFallbackHandler.swift deleted file mode 100644 index dea30eecd..000000000 --- a/Sources/WalletConnectRelay/SocketUrlFallbackHandler.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation - -class SocketUrlFallbackHandler { - private let relayUrlFactory: RelayUrlFactory - private var logger: ConsoleLogging - private var socket: WebSocketConnecting - private let networkMonitor: NetworkMonitoring - var onTryReconnect: (()->())? - - init( - relayUrlFactory: RelayUrlFactory, - logger: ConsoleLogging, - socket: WebSocketConnecting, - networkMonitor: NetworkMonitoring) { - self.relayUrlFactory = relayUrlFactory - self.logger = logger - self.socket = socket - self.networkMonitor = networkMonitor - } - - func handleFallbackIfNeeded(error: NetworkError) { - if error == .connectionFailed && socket.request.url?.host == NetworkConstants.defaultUrl { - logger.debug("[WebSocket] - Fallback to \(NetworkConstants.fallbackUrl)") - relayUrlFactory.setFallback() - socket.request.url = relayUrlFactory.create() - onTryReconnect?() - } - } -} diff --git a/Sources/WalletConnectSign/Auth/Link/LinkAuthRequester.swift b/Sources/WalletConnectSign/Auth/Link/LinkAuthRequester.swift index c18b9fb78..228baef25 100644 --- a/Sources/WalletConnectSign/Auth/Link/LinkAuthRequester.swift +++ b/Sources/WalletConnectSign/Auth/Link/LinkAuthRequester.swift @@ -31,7 +31,6 @@ actor LinkAuthRequester { } func request(params: AuthRequestParams, walletUniversalLink: String) async throws -> String { - guard try linkModeLinksStore.get(key: walletUniversalLink) != nil else { throw Errors.walletLinkSupportNotProven } var params = params diff --git a/Sources/WalletConnectSign/Auth/Link/LinkEnvelopesDispatcher.swift b/Sources/WalletConnectSign/Auth/Link/LinkEnvelopesDispatcher.swift index bd7d093a1..5eec32a99 100644 --- a/Sources/WalletConnectSign/Auth/Link/LinkEnvelopesDispatcher.swift +++ b/Sources/WalletConnectSign/Auth/Link/LinkEnvelopesDispatcher.swift @@ -129,7 +129,7 @@ final class LinkEnvelopesDispatcher { } private func serializeAndCreateUrl(peerUniversalLink: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType, topic: String) throws -> URL { - let envelope = try serializer.serialize(topic: topic, encodable: encodable, envelopeType: envelopeType) + let envelope = try serializer.serialize(topic: topic, encodable: encodable, envelopeType: envelopeType, codingType: .base64UrlEncoded) guard var components = URLComponents(string: peerUniversalLink) else { throw URLError(.badURL) } diff --git a/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift b/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift index 578b6a75e..03f0456be 100644 --- a/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift +++ b/Sources/WalletConnectSign/Auth/Services/App/AuthResponseSubscriber.swift @@ -97,6 +97,8 @@ class AuthResponseSubscriber { linkEnvelopesDispatcher.responseSubscription(on: SessionAuthenticatedProtocolMethod.responseApprove()) .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + _ = getTransportTypeUpgradeIfPossible(peerMetadata: payload.response.responder.metadata, requestId: payload.id) + let pairingTopic = payload.topic pairingRegisterer.activate(pairingTopic: pairingTopic, peerMetadata: nil) removeResponseTopicRecord(responseTopic: payload.topic) diff --git a/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift b/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift index a4b229830..dea7e0e9e 100644 --- a/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift +++ b/Sources/WalletConnectSign/Auth/Services/Wallet/SessionAuthenticateResponder.swift @@ -11,6 +11,7 @@ actor SessionAuthenticateResponder { private let pairingRegisterer: PairingRegisterer private let metadata: AppMetadata private let util: ApproveSessionAuthenticateUtil + private let eventsClient: EventsClientProtocol init( networkingInteractor: NetworkInteracting, @@ -20,7 +21,8 @@ actor SessionAuthenticateResponder { walletErrorResponder: WalletErrorResponder, pairingRegisterer: PairingRegisterer, metadata: AppMetadata, - approveSessionAuthenticateUtil: ApproveSessionAuthenticateUtil + approveSessionAuthenticateUtil: ApproveSessionAuthenticateUtil, + eventsClient: EventsClientProtocol ) { self.networkingInteractor = networkingInteractor self.logger = logger @@ -30,60 +32,119 @@ actor SessionAuthenticateResponder { self.pairingRegisterer = pairingRegisterer self.metadata = metadata self.util = approveSessionAuthenticateUtil + self.eventsClient = eventsClient } func respond(requestId: RPCID, auths: [Cacao]) async throws -> Session? { - try await util.recoverAndVerifySignature(cacaos: auths) - let (sessionAuthenticateRequestParams, pairingTopic) = try util.getsessionAuthenticateRequestParams(requestId: requestId) - let (responseTopic, responseKeys) = try util.generateAgreementKeys(requestParams: sessionAuthenticateRequestParams) - - - try kms.setAgreementSecret(responseKeys, topic: responseTopic) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.signatureVerificationStarted) + do { + try await util.recoverAndVerifySignature(cacaos: auths) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.signatureVerificationSuccess) + } catch { + eventsClient.saveEvent(SessionAuthenticateErrorEvents.signatureVerificationFailed) + throw error + } + + let sessionAuthenticateRequestParams: SessionAuthenticateRequestParams + let pairingTopic: String + + do { + (sessionAuthenticateRequestParams, pairingTopic) = try util.getsessionAuthenticateRequestParams(requestId: requestId) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.requestParamsRetrieved) + } catch { + eventsClient.saveEvent(SessionAuthenticateErrorEvents.requestParamsRetrievalFailed) + throw error + } + + let responseTopic: String + let responseKeys: AgreementKeys + + do { + (responseTopic, responseKeys) = try util.generateAgreementKeys(requestParams: sessionAuthenticateRequestParams) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.agreementKeysGenerated) + } catch { + eventsClient.saveEvent(SessionAuthenticateErrorEvents.agreementKeysGenerationFailed) + throw error + } + + do { + try kms.setAgreementSecret(responseKeys, topic: responseTopic) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.agreementSecretSet) + } catch { + eventsClient.saveEvent(SessionAuthenticateErrorEvents.agreementSecretSetFailed) + throw error + } let peerParticipant = sessionAuthenticateRequestParams.requester - let sessionSelfPubKey = try kms.createX25519KeyPair() - let sessionSelfPubKeyHex = sessionSelfPubKey.hexRepresentation - let sessionKeys = try kms.performKeyAgreement(selfPublicKey: sessionSelfPubKey, peerPublicKey: peerParticipant.publicKey) + let sessionSelfPubKey: AgreementPublicKey + let sessionSelfPubKeyHex: String + let sessionKeys: AgreementKeys + + do { + sessionSelfPubKey = try kms.createX25519KeyPair() + sessionSelfPubKeyHex = sessionSelfPubKey.hexRepresentation + sessionKeys = try kms.performKeyAgreement(selfPublicKey: sessionSelfPubKey, peerPublicKey: peerParticipant.publicKey) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.sessionKeysGenerated) + } catch { + eventsClient.saveEvent(SessionAuthenticateErrorEvents.sessionKeysGenerationFailed) + throw error + } let sessionTopic = sessionKeys.derivedTopic() - try kms.setAgreementSecret(sessionKeys, topic: sessionTopic) + do { + try kms.setAgreementSecret(sessionKeys, topic: sessionTopic) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.sessionSecretSet) + } catch { + eventsClient.saveEvent(SessionAuthenticateErrorEvents.sessionSecretSetFailed) + throw error + } let selfParticipant = Participant(publicKey: sessionSelfPubKeyHex, metadata: metadata) let responseParams = SessionAuthenticateResponseParams(responder: selfParticipant, cacaos: auths) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.responseParamsCreated) let response = RPCResponse(id: requestId, result: responseParams) - - try await networkingInteractor.respond( - topic: responseTopic, - response: response, - protocolMethod: SessionAuthenticatedProtocolMethod.responseApprove(), - envelopeType: .type1(pubKey: responseKeys.publicKey.rawRepresentation) - ) - - - let session = try util.createSession( - response: responseParams, - pairingTopic: pairingTopic, - request: sessionAuthenticateRequestParams, - sessionTopic: sessionTopic, - transportType: .relay - ) - - pairingRegisterer.activate( - pairingTopic: pairingTopic, - peerMetadata: sessionAuthenticateRequestParams.requester.metadata - ) - verifyContextStore.delete(forKey: requestId.string) - - return session + do { + try await networkingInteractor.respond( + topic: responseTopic, + response: response, + protocolMethod: SessionAuthenticatedProtocolMethod.responseApprove(), + envelopeType: .type1(pubKey: responseKeys.publicKey.rawRepresentation) + ) + eventsClient.saveEvent(SessionAuthenticateTraceEvents.responseSent) + } catch { + eventsClient.saveEvent(SessionAuthenticateErrorEvents.responseSendFailed) + throw error + } + + do { + let session = try util.createSession( + response: responseParams, + pairingTopic: pairingTopic, + request: sessionAuthenticateRequestParams, + sessionTopic: sessionTopic, + transportType: .relay + ) + pairingRegisterer.activate( + pairingTopic: pairingTopic, + peerMetadata: sessionAuthenticateRequestParams.requester.metadata + ) + verifyContextStore.delete(forKey: requestId.string) + return session + } catch { + eventsClient.saveEvent(SessionAuthenticateErrorEvents.sessionCreationFailed) + throw error + } } func respondError(requestId: RPCID) async throws { - try await walletErrorResponder.respondError(AuthError.userRejeted, requestId: requestId) + do { + try await walletErrorResponder.respondError(AuthError.userRejeted, requestId: requestId) + } catch { + throw error + } verifyContextStore.delete(forKey: requestId.string) } } - - diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index eabb88d6b..cd127d81a 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -30,6 +30,7 @@ final class ApproveEngine { private let logger: ConsoleLogging private let rpcHistory: RPCHistory private let authRequestSubscribersTracking: AuthRequestSubscribersTracking + private let eventsClient: EventsClientProtocol private var publishers = Set() @@ -46,7 +47,8 @@ final class ApproveEngine { sessionStore: WCSessionStorage, verifyClient: VerifyClientProtocol, rpcHistory: RPCHistory, - authRequestSubscribersTracking: AuthRequestSubscribersTracking + authRequestSubscribersTracking: AuthRequestSubscribersTracking, + eventsClient: EventsClientProtocol ) { self.networkingInteractor = networkingInteractor self.proposalPayloadsStore = proposalPayloadsStore @@ -61,50 +63,72 @@ final class ApproveEngine { self.verifyClient = verifyClient self.rpcHistory = rpcHistory self.authRequestSubscribersTracking = authRequestSubscribersTracking + self.eventsClient = eventsClient setupRequestSubscriptions() setupResponseSubscriptions() setupResponseErrorSubscriptions() } + func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil) async throws -> Session { + eventsClient.startTrace(topic: "") logger.debug("Approving session proposal") + eventsClient.saveEvent(SessionApproveExecutionTraceEvents.approvingSessionProposal) - guard !sessionNamespaces.isEmpty else { throw Errors.emtySessionNamespacesForbidden } + guard !sessionNamespaces.isEmpty else { + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.sessionNamespacesValidationFailure) + throw Errors.emtySessionNamespacesForbidden + } guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.proposalNotFound) throw Errors.proposalNotFound } + let pairingTopic = payload.topic + + eventsClient.setTopic(pairingTopic) let proposal = payload.request guard !proposal.isExpired() else { logger.debug("Proposal has expired, topic: \(payload.topic)") + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.proposalExpired) proposalPayloadsStore.delete(forKey: proposerPubKey) throw Errors.proposalExpired } let networkConnectionStatus = await resolveNetworkConnectionStatus() guard networkConnectionStatus == .connected else { + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.networkNotConnected) throw Errors.networkNotConnected } - let pairingTopic = payload.topic - - try Namespace.validate(sessionNamespaces) - try Namespace.validateApproved(sessionNamespaces, against: proposal.requiredNamespaces) + do { + eventsClient.saveEvent(SessionApproveExecutionTraceEvents.sessionNamespacesValidationStarted) + try Namespace.validate(sessionNamespaces) + try Namespace.validateApproved(sessionNamespaces, against: proposal.requiredNamespaces) + eventsClient.saveEvent(SessionApproveExecutionTraceEvents.sessionNamespacesValidationSuccess) + } catch { + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.sessionNamespacesValidationFailure) + throw error + } let selfPublicKey = try kms.createX25519KeyPair() guard let agreementKey = try? kms.performKeyAgreement( selfPublicKey: selfPublicKey, peerPublicKey: proposal.proposer.publicKey - ) else { throw Errors.agreementMissingOrInvalid } + ) else { + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.agreementMissingOrInvalid) + throw Errors.agreementMissingOrInvalid + } let sessionTopic = agreementKey.derivedTopic() try kms.setAgreementSecret(agreementKey, topic: sessionTopic) guard let relay = proposal.relays.first else { + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.relayNotFound) throw Errors.relayNotFound } @@ -125,21 +149,33 @@ final class ApproveEngine { pairingTopic: pairingTopic ) - _ = try await proposeResponseTask - let session: WCSession = try await settleRequestTask + do { + _ = try await proposeResponseTask + eventsClient.saveEvent(SessionApproveExecutionTraceEvents.responseApproveSent) + } catch { + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.sessionSettleFailure) + throw error + } - sessionStore.setSession(session) - onSessionSettle?(session.publicRepresentation()) - logger.debug("Session proposal response and settle request have been sent") + do { + let session: WCSession = try await settleRequestTask + sessionStore.setSession(session) + onSessionSettle?(session.publicRepresentation()) + eventsClient.saveEvent(SessionApproveExecutionTraceEvents.sessionSettleSuccess) + logger.debug("Session proposal response and settle request have been sent") - proposalPayloadsStore.delete(forKey: proposerPubKey) - verifyContextStore.delete(forKey: proposerPubKey) + proposalPayloadsStore.delete(forKey: proposerPubKey) + verifyContextStore.delete(forKey: proposerPubKey) - pairingRegisterer.activate( - pairingTopic: payload.topic, - peerMetadata: payload.request.proposer.metadata - ) - return session.publicRepresentation() + pairingRegisterer.activate( + pairingTopic: payload.topic, + peerMetadata: payload.request.proposer.metadata + ) + return session.publicRepresentation() + } catch { + eventsClient.saveEvent(ApproveSessionTraceErrorEvents.sessionSettleFailure) + throw error + } } func reject(proposerPubKey: String, reason: SignReasonCode) async throws { diff --git a/Sources/WalletConnectSign/Sign/Sign.swift b/Sources/WalletConnectSign/Sign/Sign.swift index 25f88df3e..3205485d6 100644 --- a/Sources/WalletConnectSign/Sign/Sign.swift +++ b/Sources/WalletConnectSign/Sign/Sign.swift @@ -29,7 +29,8 @@ public class Sign { projectId: Networking.projectId, crypto: config.crypto, networkingClient: Networking.interactor, - groupIdentifier: Networking.groupIdentifier + groupIdentifier: Networking.groupIdentifier, + eventsClient: Events.instance ) }() diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift index 4acff725c..6a9989112 100644 --- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift +++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift @@ -17,7 +17,8 @@ public struct SignClientFactory { projectId: String, crypto: CryptoProvider, networkingClient: NetworkingInteractor, - groupIdentifier: String + groupIdentifier: String, + eventsClient: EventsClientProtocol ) -> SignClient { let logger = ConsoleLogger(prefix: "📝", loggingLevel: .off) @@ -37,7 +38,8 @@ public struct SignClientFactory { networkingClient: networkingClient, iatProvider: iatProvider, projectId: projectId, - crypto: crypto + crypto: crypto, + eventsClient: eventsClient ) } @@ -50,7 +52,8 @@ public struct SignClientFactory { networkingClient: NetworkingInteractor, iatProvider: IATProvider, projectId: String, - crypto: CryptoProvider + crypto: CryptoProvider, + eventsClient: EventsClientProtocol ) -> SignClient { let kms = KeyManagementService(keychain: keychainStorage) let rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) @@ -83,7 +86,8 @@ public struct SignClientFactory { sessionStore: sessionStore, verifyClient: verifyClient, rpcHistory: rpcHistory, - authRequestSubscribersTracking: authRequestSubscribersTracking + authRequestSubscribersTracking: authRequestSubscribersTracking, + eventsClient: eventsClient ) let cleanupService = SignCleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionTopicToProposal: sessionTopicToProposal, networkInteractor: networkingClient, rpcHistory: rpcHistory) let deleteSessionService = DeleteSessionService(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger) @@ -125,7 +129,7 @@ public struct SignClientFactory { let linkAuthRequester = LinkAuthRequester(kms: kms, appMetadata: metadata, logger: logger, iatProvader: iatProvider, authResponseTopicRecordsStore: authResponseTopicRecordsStore, linkEnvelopesDispatcher: linkEnvelopesDispatcher, linkModeLinksStore: linkModeLinksStore) let linkAuthRequestSubscriber = LinkAuthRequestSubscriber(logger: logger, kms: kms, envelopesDispatcher: linkEnvelopesDispatcher, verifyClient: verifyClient, verifyContextStore: verifyContextStore) - let relaySessionAuthenticateResponder = SessionAuthenticateResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, verifyContextStore: verifyContextStore, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingClient, metadata: metadata, approveSessionAuthenticateUtil: approveSessionAuthenticateUtil) + let relaySessionAuthenticateResponder = SessionAuthenticateResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, verifyContextStore: verifyContextStore, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingClient, metadata: metadata, approveSessionAuthenticateUtil: approveSessionAuthenticateUtil, eventsClient: eventsClient) let linkSessionAuthenticateResponder = LinkSessionAuthenticateResponder(linkEnvelopesDispatcher: linkEnvelopesDispatcher, logger: logger, kms: kms, metadata: metadata, approveSessionAuthenticateUtil: approveSessionAuthenticateUtil, walletErrorResponder: walletErrorResponder) diff --git a/Sources/WalletConnectSign/Sign/SignImports.swift b/Sources/WalletConnectSign/Sign/SignImports.swift index 91463cad3..898b4f971 100644 --- a/Sources/WalletConnectSign/Sign/SignImports.swift +++ b/Sources/WalletConnectSign/Sign/SignImports.swift @@ -2,4 +2,6 @@ @_exported import WalletConnectPairing @_exported import WalletConnectSigner @_exported import WalletConnectVerify +@_exported import WalletConnectRelay +@_exported import Events #endif diff --git a/Tests/EventsTests/EventsCollectorTests.swift b/Tests/EventsTests/EventsCollectorTests.swift new file mode 100644 index 000000000..3dc59c7f9 --- /dev/null +++ b/Tests/EventsTests/EventsCollectorTests.swift @@ -0,0 +1,50 @@ +import Foundation +import XCTest +@testable import Events + +class EventsCollectorTests: XCTestCase { + + var mockStorage: MockEventStorage! + var eventsCollector: EventsCollector! + + override func setUp() { + super.setUp() + mockStorage = MockEventStorage() + eventsCollector = EventsCollector(storage: mockStorage, logger: ConsoleLoggerMock()) + } + + override func tearDown() { + eventsCollector = nil + mockStorage = nil + super.tearDown() + } + + func testStartTrace() { + eventsCollector.startTrace(topic: "test_topic") + XCTAssertEqual(eventsCollector.topic, "test_topic") + XCTAssertEqual(eventsCollector.trace.count, 0) + } + + func testSaveEvent() { + eventsCollector.startTrace(topic: "test_topic") + eventsCollector.saveEvent(PairingExecutionTraceEvents.pairingStarted) + + XCTAssertEqual(eventsCollector.trace, ["pairing_started"]) + XCTAssertEqual(mockStorage.savedEvents.count, 0) + } + + func testSaveErrorEvent() { + eventsCollector.startTrace(topic: "test_topic") + eventsCollector.saveEvent(PairingExecutionTraceEvents.pairingStarted) + eventsCollector.saveEvent(PairingTraceErrorEvents.noInternetConnection) + + XCTAssertEqual(mockStorage.savedEvents.count, 1) + let savedEvent = mockStorage.savedEvents.first + XCTAssertNotNil(savedEvent) + XCTAssertEqual(savedEvent?.props.type, "no_internet_connection") + XCTAssertEqual(savedEvent?.props.properties?.topic, "test_topic") + XCTAssertEqual(savedEvent?.props.properties?.trace, ["pairing_started", "no_internet_connection"]) + XCTAssertNil(eventsCollector.topic) + XCTAssertEqual(eventsCollector.trace.count, 0) + } +} diff --git a/Tests/EventsTests/EventsDispatcherTests.swift b/Tests/EventsTests/EventsDispatcherTests.swift new file mode 100644 index 000000000..e7b980e5a --- /dev/null +++ b/Tests/EventsTests/EventsDispatcherTests.swift @@ -0,0 +1,38 @@ +import XCTest +@testable import Events + +class EventsDispatcherTests: XCTestCase { + var mockNetworkingService: MockNetworkingService! + var eventsDispatcher: EventsDispatcher! + let events = [Event(eventId: UUID().uuidString, bundleId: "com.wallet.example", timestamp: Int64(Date().timeIntervalSince1970 * 1000), props: Props(event: "ERROR", type: "test_error", properties: Properties(topic: "test_topic", trace: ["test_trace"])))] + + override func setUp() { + super.setUp() + mockNetworkingService = MockNetworkingService() + let retryPolicy = RetryPolicy(maxAttempts: 3, initialDelay: 1, multiplier: 1.5, delayOverride: 0.001) + eventsDispatcher = EventsDispatcher(networkingService: mockNetworkingService, retryPolicy: retryPolicy) + } + + override func tearDown() { + eventsDispatcher = nil + mockNetworkingService = nil + super.tearDown() + } + + func testRetrySuccess() async throws { + mockNetworkingService.shouldFail = true + do { + _ = try await eventsDispatcher.executeWithRetry(events: events) + XCTFail("Expected to throw an error") + } catch { + XCTAssertEqual(mockNetworkingService.attemptCount, 3) + } + } + + func testRetryFailure() async throws { + mockNetworkingService.shouldFail = false + let result = try await eventsDispatcher.executeWithRetry(events: events) + XCTAssertEqual(result, true) + XCTAssertEqual(mockNetworkingService.attemptCount, 1) + } +} diff --git a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift index b29a830ba..368d25da4 100644 --- a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift +++ b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift @@ -28,14 +28,12 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase { socketAuthenticator: socketAuthenticator ) backgroundTaskRegistrar = BackgroundTaskRegistrarMock() - let socketUrlFallbackHandler = SocketUrlFallbackHandler(relayUrlFactory: relayUrlFactory, logger: ConsoleLoggerMock(), socket: webSocket, networkMonitor: networkMonitor) sut = AutomaticSocketConnectionHandler( socket: webSocketSession, networkMonitor: networkMonitor, appStateObserver: appStateObserver, backgroundTaskRegistrar: backgroundTaskRegistrar, - logger: ConsoleLoggerMock(), - socketUrlFallbackHandler: socketUrlFallbackHandler + logger: ConsoleLoggerMock() ) } diff --git a/Tests/RelayerTests/DispatcherTests.swift b/Tests/RelayerTests/DispatcherTests.swift index 4a58cfd97..e8b0de168 100644 --- a/Tests/RelayerTests/DispatcherTests.swift +++ b/Tests/RelayerTests/DispatcherTests.swift @@ -71,8 +71,7 @@ final class DispatcherTests: XCTestCase { projectId: "1012db890cf3cfb0c1cdc929add657ba", socketAuthenticator: socketAuthenticator ) - let socketUrlFallbackHandler = SocketUrlFallbackHandler(relayUrlFactory: relayUrlFactory, logger: logger, socket: webSocket, networkMonitor: networkMonitor) - let socketConnectionHandler = ManualSocketConnectionHandler(socket: webSocket, logger: logger, socketUrlFallbackHandler: socketUrlFallbackHandler) + let socketConnectionHandler = ManualSocketConnectionHandler(socket: webSocket, logger: logger) sut = Dispatcher( socketFactory: webSocketFactory, relayUrlFactory: relayUrlFactory, diff --git a/Tests/RelayerTests/ManualSocketConnectionHandlerTests.swift b/Tests/RelayerTests/ManualSocketConnectionHandlerTests.swift index 6f8a939cb..d86fbc9cf 100644 --- a/Tests/RelayerTests/ManualSocketConnectionHandlerTests.swift +++ b/Tests/RelayerTests/ManualSocketConnectionHandlerTests.swift @@ -22,9 +22,8 @@ final class ManualSocketConnectionHandlerTests: XCTestCase { projectId: "1012db890cf3cfb0c1cdc929add657ba", socketAuthenticator: socketAuthenticator ) - let socketUrlFallbackHandler = SocketUrlFallbackHandler(relayUrlFactory: relayUrlFactory, logger: ConsoleLoggerMock(), socket: socket, networkMonitor: networkMonitor) - sut = ManualSocketConnectionHandler(socket: socket, logger: ConsoleLoggerMock(), socketUrlFallbackHandler: socketUrlFallbackHandler) + sut = ManualSocketConnectionHandler(socket: socket, logger: ConsoleLoggerMock()) } func testHandleDisconnect() { diff --git a/Tests/RelayerTests/Mocks/DispatcherMock.swift b/Tests/RelayerTests/Mocks/DispatcherMock.swift index 2e12b6ab2..82dd12c6c 100644 --- a/Tests/RelayerTests/Mocks/DispatcherMock.swift +++ b/Tests/RelayerTests/Mocks/DispatcherMock.swift @@ -4,6 +4,8 @@ import Combine @testable import WalletConnectRelay class DispatcherMock: Dispatching { + var isSocketConnected: Bool = true + private var publishers = Set() private let socketConnectionStatusPublisherSubject = CurrentValueSubject(.disconnected) diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index a1fa196ad..65b73ab0c 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -6,6 +6,8 @@ import WalletConnectKMS import WalletConnectNetworking public class NetworkingInteractorMock: NetworkInteracting { + public var isSocketConnected: Bool = true + private var publishers = Set() diff --git a/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift b/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift index 6def56757..1aeee96ec 100644 --- a/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift +++ b/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift @@ -5,7 +5,7 @@ import XCTest import WalletConnectUtils import WalletConnectNetworking -final class WalletPairServiceTestsTests: XCTestCase { +final class WalletPairServiceTests: XCTestCase { var service: WalletPairService! var networkingInteractor: NetworkingInteractorMock! @@ -18,7 +18,7 @@ final class WalletPairServiceTestsTests: XCTestCase { storageMock = WCPairingStorageMock() cryptoMock = KeyManagementServiceMock() rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: RuntimeKeyValueStorage()) - service = WalletPairService(networkingInteractor: networkingInteractor, kms: cryptoMock, pairingStorage: storageMock, history: rpcHistory, logger: ConsoleLoggerMock()) + service = WalletPairService(networkingInteractor: networkingInteractor, kms: cryptoMock, pairingStorage: storageMock, history: rpcHistory, logger: ConsoleLoggerMock(), eventsClient: MockEventsClient()) } func testPairWhenNetworkNotConnectedThrows() async { diff --git a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift index 18a392325..ae3a4772d 100644 --- a/Tests/WalletConnectSignTests/AppProposalServiceTests.swift +++ b/Tests/WalletConnectSignTests/AppProposalServiceTests.swift @@ -78,7 +78,8 @@ final class AppProposalServiceTests: XCTestCase { sessionStore: WCSessionStorageMock(), verifyClient: VerifyClientMock(), rpcHistory: history, - authRequestSubscribersTracking: AuthRequestSubscribersTracking(logger: logger) + authRequestSubscribersTracking: AuthRequestSubscribersTracking(logger: logger), + eventsClient: MockEventsClient() ) } diff --git a/Tests/WalletConnectSignTests/ApproveEngineTests.swift b/Tests/WalletConnectSignTests/ApproveEngineTests.swift index 585f7e4be..79b2f7996 100644 --- a/Tests/WalletConnectSignTests/ApproveEngineTests.swift +++ b/Tests/WalletConnectSignTests/ApproveEngineTests.swift @@ -52,7 +52,8 @@ final class ApproveEngineTests: XCTestCase { sessionStore: sessionStorageMock, verifyClient: VerifyClientMock(), rpcHistory: history, - authRequestSubscribersTracking: AuthRequestSubscribersTracking(logger: ConsoleLoggerMock()) + authRequestSubscribersTracking: AuthRequestSubscribersTracking(logger: ConsoleLoggerMock()), + eventsClient: MockEventsClient() ) }