Skip to content

Commit

Permalink
Merge pull request #1284 from WalletConnect/feature/network-monitor-i…
Browse files Browse the repository at this point in the history
…n-dispatcher

[Core] Network monitor in dispatcher
  • Loading branch information
flypaper0 authored Jan 24, 2024
2 parents 880b82a + e816d31 commit 2631aa4
Show file tree
Hide file tree
Showing 18 changed files with 82 additions and 27 deletions.
1 change: 1 addition & 0 deletions Example/IntegrationTests/Auth/AuthTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ final class AuthTests: XCTestCase {
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
socketFactory: DefaultSocketFactory(),
networkMonitor: NetworkMonitor(),
logger: logger)

let networkingClient = NetworkingClientFactory.create(
Expand Down
1 change: 1 addition & 0 deletions Example/IntegrationTests/Chat/ChatTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ final class ChatTests: XCTestCase {
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
socketFactory: DefaultSocketFactory(),
networkMonitor: NetworkMonitor(),
logger: logger)

let networkingInteractor = NetworkingClientFactory.create(
Expand Down
1 change: 1 addition & 0 deletions Example/IntegrationTests/Pairing/PairingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ final class PairingTests: XCTestCase {
keyValueStorage: RuntimeKeyValueStorage(),
keychainStorage: keychain,
socketFactory: DefaultSocketFactory(),
networkMonitor: NetworkMonitor(),
logger: logger)

let networkingClient = NetworkingClientFactory.create(
Expand Down
1 change: 1 addition & 0 deletions Example/IntegrationTests/Push/NotifyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ final class NotifyTests: XCTestCase {
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
socketFactory: DefaultSocketFactory(),
networkMonitor: NetworkMonitor(),
logger: relayLogger)

let networkingClient = NetworkingClientFactory.create(
Expand Down
1 change: 1 addition & 0 deletions Example/IntegrationTests/Sign/SignClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ final class SignClientTests: XCTestCase {
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
socketFactory: DefaultSocketFactory(),
networkMonitor: NetworkMonitor(),
logger: logger
)

Expand Down
1 change: 1 addition & 0 deletions Example/IntegrationTests/Sync/SyncTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ final class SyncTests: XCTestCase {
keyValueStorage: RuntimeKeyValueStorage(),
keychainStorage: keychain,
socketFactory: DefaultSocketFactory(),
networkMonitor: NetworkMonitor(),
logger: logger)
let networkingInteractor = NetworkingClientFactory.create(
relayClient: relayClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ final class XPlatformW3WTests: XCTestCase {
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
socketFactory: DefaultSocketFactory(),
networkMonitor: NetworkMonitor(),
logger: relayLogger
)

Expand Down
5 changes: 4 additions & 1 deletion Example/RelayIntegrationTests/RelayClientEndToEndTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,11 @@ final class RelayClientEndToEndTests: XCTestCase {
)
let socket = WebSocket(url: urlFactory.create(fallback: false))
let webSocketFactory = WebSocketFactoryMock(webSocket: socket)
let networkMonitor = NetworkMonitor()
let dispatcher = Dispatcher(
socketFactory: webSocketFactory,
relayUrlFactory: urlFactory,
networkMonitor: networkMonitor,
socketConnectionType: .manual,
logger: logger
)
Expand All @@ -57,7 +59,8 @@ final class RelayClientEndToEndTests: XCTestCase {
keyValueStorage: keyValueStorage,
keychainStorage: keychain,
socketFactory: DefaultSocketFactory(),
socketConnectionType: .manual,
socketConnectionType: .manual,
networkMonitor: networkMonitor,
logger: logger
)
let clientId = try! relayClient.getClientId()
Expand Down
22 changes: 16 additions & 6 deletions Sources/WalletConnectNetworking/NetworkingInteractor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ public class NetworkingInteractor: NetworkInteracting {
public var networkConnectionStatusPublisher: AnyPublisher<NetworkConnectionStatus, Never>
public var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never>

private let networkMonitor: NetworkMonitoring

public init(
relayClient: RelayClient,
serializer: Serializing,
Expand All @@ -43,8 +41,7 @@ public class NetworkingInteractor: NetworkInteracting {
self.rpcHistory = rpcHistory
self.logger = logger
self.socketConnectionStatusPublisher = relayClient.socketConnectionStatusPublisher
self.networkMonitor = NetworkMonitor()
self.networkConnectionStatusPublisher = networkMonitor.networkConnectionStatusPublisher
self.networkConnectionStatusPublisher = relayClient.networkConnectionStatusPublisher
setupRelaySubscribtion()
}

Expand Down Expand Up @@ -205,8 +202,21 @@ public class NetworkingInteractor: NetworkInteracting {

public func request(_ request: RPCRequest, topic: String, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
try rpcHistory.set(request, forTopic: topic, emmitedBy: .local)
let message = try serializer.serialize(topic: topic, encodable: request, envelopeType: envelopeType)
try await relayClient.publish(topic: topic, payload: message, tag: protocolMethod.requestConfig.tag, prompt: protocolMethod.requestConfig.prompt, ttl: protocolMethod.requestConfig.ttl)

do {
let message = try serializer.serialize(topic: topic, encodable: request, envelopeType: envelopeType)

try await relayClient.publish(topic: topic,
payload: message,
tag: protocolMethod.requestConfig.tag,
prompt: protocolMethod.requestConfig.prompt,
ttl: protocolMethod.requestConfig.ttl)
} catch {
if let id = request.id {
rpcHistory.delete(id: id)
}
throw error
}
}

public func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws {
Expand Down
26 changes: 18 additions & 8 deletions Sources/WalletConnectRelay/Dispatching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Combine

protocol Dispatching {
var onMessage: ((String) -> Void)? { get set }
var networkConnectionStatusPublisher: AnyPublisher<NetworkConnectionStatus, Never> { get }
var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never> { get }
func send(_ string: String, completion: @escaping (Error?) -> Void)
func protectedSend(_ string: String, completion: @escaping (Error?) -> Void)
Expand All @@ -17,8 +18,9 @@ final class Dispatcher: NSObject, Dispatching {
var socketConnectionHandler: SocketConnectionHandler

private let relayUrlFactory: RelayUrlFactory
private let networkMonitor: NetworkMonitoring
private let logger: ConsoleLogging

private let defaultTimeout: Int = 5
/// The property is used to determine whether relay.walletconnect.org will be used
/// in case relay.walletconnect.com doesn't respond for some reason (most likely due to being blocked in the user's location).
Expand All @@ -30,15 +32,21 @@ final class Dispatcher: NSObject, Dispatching {
socketConnectionStatusPublisherSubject.eraseToAnyPublisher()
}

var networkConnectionStatusPublisher: AnyPublisher<NetworkConnectionStatus, Never> {
networkMonitor.networkConnectionStatusPublisher
}

private let concurrentQueue = DispatchQueue(label: "com.walletconnect.sdk.dispatcher", attributes: .concurrent)

init(
socketFactory: WebSocketFactory,
relayUrlFactory: RelayUrlFactory,
networkMonitor: NetworkMonitoring,
socketConnectionType: SocketConnectionType,
logger: ConsoleLogging
) {
self.relayUrlFactory = relayUrlFactory
self.networkMonitor = networkMonitor
self.logger = logger

let socket = socketFactory.create(with: relayUrlFactory.create(fallback: fallback))
Expand All @@ -60,7 +68,7 @@ final class Dispatcher: NSObject, Dispatching {

func send(_ string: String, completion: @escaping (Error?) -> Void) {
guard socket.isConnected else {
completion(NetworkError.webSocketNotConnected)
completion(NetworkError.connectionFailed)
return
}
socket.write(string: string) {
Expand All @@ -69,20 +77,22 @@ final class Dispatcher: NSObject, Dispatching {
}

func protectedSend(_ string: String, completion: @escaping (Error?) -> Void) {
guard !socket.isConnected else {
guard !socket.isConnected || !networkMonitor.isConnected else {
return send(string, completion: completion)
}

var cancellable: AnyCancellable?
cancellable = socketConnectionStatusPublisher
.filter { $0 == .connected }
cancellable = Publishers.CombineLatest(socketConnectionStatusPublisher, networkConnectionStatusPublisher)
.filter { $0.0 == .connected && $0.1 == .connected }
.setFailureType(to: NetworkError.self)
.timeout(.seconds(defaultTimeout), scheduler: concurrentQueue, customError: { .webSocketNotConnected })
.timeout(.seconds(defaultTimeout), scheduler: concurrentQueue, customError: { .connectionFailed })
.sink(receiveCompletion: { [unowned self] result in
switch result {
case .failure(let error):
cancellable?.cancel()
self.handleFallbackIfNeeded(error: error)
if !socket.isConnected {
handleFallbackIfNeeded(error: error)
}
completion(error)
case .finished: break
}
Expand Down Expand Up @@ -137,7 +147,7 @@ extension Dispatcher {
}

private func handleFallbackIfNeeded(error: NetworkError) {
if error == .webSocketNotConnected && socket.request.url?.host == NetworkConstants.defaultUrl {
if error == .connectionFailed && socket.request.url?.host == NetworkConstants.defaultUrl {
logger.debug("[WebSocket] - Fallback to \(NetworkConstants.fallbackUrl)")
fallback = true
socket.request.url = relayUrlFactory.create(fallback: fallback)
Expand Down
8 changes: 4 additions & 4 deletions Sources/WalletConnectRelay/Misc/NetworkError.swift
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import Foundation

enum NetworkError: Error, Equatable {
case webSocketNotConnected
case connectionFailed
case sendMessageFailed(Error)
case receiveMessageFailure(Error)

static func == (lhs: NetworkError, rhs: NetworkError) -> Bool {
switch (lhs, rhs) {
case (.webSocketNotConnected, .webSocketNotConnected): return true
case (.connectionFailed, .connectionFailed): return true
case (.sendMessageFailed, .sendMessageFailed): return true
case (.receiveMessageFailure, .receiveMessageFailure): return true
default: return false
Expand All @@ -22,8 +22,8 @@ extension NetworkError: LocalizedError {

var localizedDescription: String {
switch self {
case .webSocketNotConnected:
return "Web socket is not connected to any URL."
case .connectionFailed:
return "Web socket is not connected to any URL or networking connection error"
case .sendMessageFailed(let error):
return "Failed to send a message through the web socket: \(error)"
case .receiveMessageFailure(let error):
Expand Down
7 changes: 6 additions & 1 deletion Sources/WalletConnectRelay/NetworkMonitoring.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public enum NetworkConnectionStatus {
}

public protocol NetworkMonitoring: AnyObject {
var isConnected: Bool { get }
var networkConnectionStatusPublisher: AnyPublisher<NetworkConnectionStatus, Never> { get }
}

Expand All @@ -16,7 +17,11 @@ public final class NetworkMonitor: NetworkMonitoring {
private let workerQueue = DispatchQueue(label: "com.walletconnect.sdk.network.monitor")

private let networkConnectionStatusPublisherSubject = CurrentValueSubject<NetworkConnectionStatus, Never>(.connected)


public var isConnected: Bool {
return networkConnectionStatusPublisherSubject.value == .connected
}

public var networkConnectionStatusPublisher: AnyPublisher<NetworkConnectionStatus, Never> {
networkConnectionStatusPublisherSubject
.share()
Expand Down
12 changes: 8 additions & 4 deletions Sources/WalletConnectRelay/RelayClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ public final class RelayClient {
dispatcher.socketConnectionStatusPublisher
}

public var networkConnectionStatusPublisher: AnyPublisher<NetworkConnectionStatus, Never> {
dispatcher.networkConnectionStatusPublisher
}

private let messagePublisherSubject = PassthroughSubject<(topic: String, message: String, publishedAt: Date), Never>()

private let subscriptionResponsePublisherSubject = PassthroughSubject<(RPCID?, [String]), Never>()
Expand Down Expand Up @@ -165,9 +169,9 @@ public final class RelayClient {
}
}

public func unsubscribe(topic: String, completion: @escaping ((Error?) -> Void)) {
public func unsubscribe(topic: String, completion: ((Error?) -> Void)?) {
guard let subscriptionId = subscriptions[topic] else {
completion(Errors.subscriptionIdNotFound)
completion?(Errors.subscriptionIdNotFound)
return
}
logger.debug("Unsubscribing from topic: \(topic)")
Expand All @@ -179,12 +183,12 @@ public final class RelayClient {
dispatcher.protectedSend(message) { [weak self] error in
if let error = error {
self?.logger.debug("Failed to unsubscribe from topic")
completion(error)
completion?(error)
} else {
self?.concurrentQueue.async(flags: .barrier) {
self?.subscriptions[topic] = nil
}
completion(nil)
completion?(nil)
}
}
}
Expand Down
8 changes: 7 additions & 1 deletion Sources/WalletConnectRelay/RelayClientFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@ public struct RelayClientFactory {

let logger = ConsoleLogger(prefix: "🚄" ,loggingLevel: .off)

let networkMonitor = NetworkMonitor()

return RelayClientFactory.create(
relayHost: relayHost,
projectId: projectId,
keyValueStorage: keyValueStorage,
keychainStorage: keychainStorage,
socketFactory: socketFactory,
socketConnectionType: socketConnectionType,
networkMonitor: networkMonitor,
logger: logger
)
}
Expand All @@ -39,6 +42,7 @@ public struct RelayClientFactory {
keychainStorage: KeychainStorageProtocol,
socketFactory: WebSocketFactory,
socketConnectionType: SocketConnectionType = .automatic,
networkMonitor: NetworkMonitoring,
logger: ConsoleLogging
) -> RelayClient {

Expand All @@ -52,9 +56,11 @@ public struct RelayClientFactory {
projectId: projectId,
socketAuthenticator: socketAuthenticator
)

let dispatcher = Dispatcher(
socketFactory: socketFactory,
relayUrlFactory: relayUrlFactory,
relayUrlFactory: relayUrlFactory,
networkMonitor: networkMonitor,
socketConnectionType: socketConnectionType,
logger: logger
)
Expand Down
4 changes: 3 additions & 1 deletion Tests/RelayerTests/DispatcherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ final class DispatcherTests: XCTestCase {
networkMonitor = NetworkMonitoringMock()
let defaults = RuntimeKeyValueStorage()
let logger = ConsoleLoggerMock()
let networkMonitor = NetworkMonitoringMock()
let keychainStorageMock = DispatcherKeychainStorageMock()
let clientIdStorage = ClientIdStorage(defaults: defaults, keychain: keychainStorageMock, logger: logger)
let socketAuthenticator = ClientIdAuthenticator(clientIdStorage: clientIdStorage)
Expand All @@ -72,7 +73,8 @@ final class DispatcherTests: XCTestCase {
)
sut = Dispatcher(
socketFactory: webSocketFactory,
relayUrlFactory: relayUrlFactory,
relayUrlFactory: relayUrlFactory,
networkMonitor: networkMonitor,
socketConnectionType: .manual,
logger: ConsoleLoggerMock()
)
Expand Down
2 changes: 1 addition & 1 deletion Tests/RelayerTests/Helpers/Error+Extension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ extension Error {
extension NetworkError {

var isWebSocketError: Bool {
guard case .webSocketNotConnected = self else { return false }
guard case .connectionFailed = self else { return false }
return true
}

Expand Down
4 changes: 4 additions & 0 deletions Tests/RelayerTests/Mocks/DispatcherMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import Combine
@testable import WalletConnectRelay

class DispatcherMock: Dispatching {

private var publishers = Set<AnyCancellable>()
private let socketConnectionStatusPublisherSubject = CurrentValueSubject<SocketConnectionStatus, Never>(.disconnected)
var socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never> {
return socketConnectionStatusPublisherSubject.eraseToAnyPublisher()
}
var networkConnectionStatusPublisher: AnyPublisher<NetworkConnectionStatus, Never> {
return Just(.connected).eraseToAnyPublisher()
}

var sent = false
var lastMessage: String = ""
Expand Down
4 changes: 4 additions & 0 deletions Tests/RelayerTests/Mocks/NetworkMonitoringMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ import Combine
@testable import WalletConnectRelay

class NetworkMonitoringMock: NetworkMonitoring {
var isConnected: Bool {
return true
}

var networkConnectionStatusPublisher: AnyPublisher<WalletConnectRelay.NetworkConnectionStatus, Never> {
networkConnectionStatusPublisherSubject.eraseToAnyPublisher()
}
Expand Down

0 comments on commit 2631aa4

Please sign in to comment.