Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Relay] Regenerate socket URL on error disconnection #769

Merged
merged 9 commits into from
Apr 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 34 additions & 4 deletions Example/RelayIntegrationTests/RelayClientEndToEndTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ import WalletConnectUtils
import Starscream
@testable import WalletConnectRelay

private class RelayKeychainStorageMock: KeychainStorageProtocol {
func add<T>(_ item: T, forKey key: String) throws where T : WalletConnectKMS.GenericPasswordConvertible {}
func read<T>(key: String) throws -> T where T : WalletConnectKMS.GenericPasswordConvertible {
return try T(rawRepresentation: Data())
}
func delete(key: String) throws {}
func deleteAll() throws {}
}

class WebSocketFactoryMock: WebSocketFactory {
private let webSocket: WebSocket

init(webSocket: WebSocket) {
self.webSocket = webSocket
}

func create(with url: URL) -> WebSocketConnecting {
return webSocket
}
}

final class RelayClientEndToEndTests: XCTestCase {

private var publishers = Set<AnyCancellable>()
Expand All @@ -15,11 +36,20 @@ final class RelayClientEndToEndTests: XCTestCase {
clientIdStorage: clientIdStorage,
relayHost: InputConfig.relayHost
)
let urlFactory = RelayUrlFactory(socketAuthenticator: socketAuthenticator)
let socket = WebSocket(url: urlFactory.create(host: InputConfig.relayHost, projectId: InputConfig.projectId))

let urlFactory = RelayUrlFactory(
relayHost: InputConfig.relayHost,
projectId: InputConfig.projectId,
socketAuthenticator: socketAuthenticator
)
let socket = WebSocket(url: urlFactory.create())
let webSocketFactory = WebSocketFactoryMock(webSocket: socket)
let logger = ConsoleLogger()
let dispatcher = Dispatcher(socket: socket, socketConnectionHandler: ManualSocketConnectionHandler(socket: socket), logger: logger)
let dispatcher = Dispatcher(
socketFactory: webSocketFactory,
relayUrlFactory: urlFactory,
socketConnectionType: .manual,
logger: logger
)
return RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage(), clientIdStorage: clientIdStorage)
}

Expand Down
42 changes: 29 additions & 13 deletions Sources/WalletConnectRelay/Dispatching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ final class Dispatcher: NSObject, Dispatching {
var onMessage: ((String) -> Void)?
var socket: WebSocketConnecting
var socketConnectionHandler: SocketConnectionHandler


private let relayUrlFactory: RelayUrlFactory
private let logger: ConsoleLogging

private let defaultTimeout: Int = 5

private let socketConnectionStatusPublisherSubject = CurrentValueSubject<SocketConnectionStatus, Never>(.disconnected)
Expand All @@ -27,24 +29,36 @@ final class Dispatcher: NSObject, Dispatching {

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

init(socket: WebSocketConnecting,
socketConnectionHandler: SocketConnectionHandler,
logger: ConsoleLogging) {
self.socket = socket
init(
socketFactory: WebSocketFactory,
relayUrlFactory: RelayUrlFactory,
socketConnectionType: SocketConnectionType,
logger: ConsoleLogging
) {
self.relayUrlFactory = relayUrlFactory
self.logger = logger
self.socketConnectionHandler = socketConnectionHandler

let socket = socketFactory.create(with: relayUrlFactory.create())
socket.request.addValue(EnvironmentInfo.userAgent, forHTTPHeaderField: "User-Agent")
self.socket = socket

switch socketConnectionType {
case .automatic: socketConnectionHandler = AutomaticSocketConnectionHandler(socket: socket)
case .manual: socketConnectionHandler = ManualSocketConnectionHandler(socket: socket)
}

super.init()
setUpWebSocketSession()
setUpSocketConnectionObserving()
}

func send(_ string: String, completion: @escaping (Error?) -> Void) {
if socket.isConnected {
self.socket.write(string: string) {
completion(nil)
}
} else {
guard socket.isConnected else {
completion(NetworkError.webSocketNotConnected)
return
}
socket.write(string: string) {
completion(nil)
}
}

Expand Down Expand Up @@ -101,9 +115,11 @@ final class Dispatcher: NSObject, Dispatching {
socket.onConnect = { [unowned self] in
self.socketConnectionStatusPublisherSubject.send(.connected)
}
socket.onDisconnect = { [unowned self] _ in
socket.onDisconnect = { [unowned self] error in
self.socketConnectionStatusPublisherSubject.send(.disconnected)

if error != nil {
self.socket.request.url = relayUrlFactory.create()
}
Task(priority: .high) {
await self.socketConnectionHandler.handleDisconnection()
}
Expand Down
25 changes: 11 additions & 14 deletions Sources/WalletConnectRelay/RelayClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,20 +84,17 @@ public final class RelayClient {
clientIdStorage: clientIdStorage,
relayHost: relayHost
)
let relayUrlFactory = RelayUrlFactory(socketAuthenticator: socketAuthenticator)
let socket = socketFactory.create(with: relayUrlFactory.create(
host: relayHost,
projectId: projectId
))
socket.request.addValue(EnvironmentInfo.userAgent, forHTTPHeaderField: "User-Agent")
let socketConnectionHandler: SocketConnectionHandler
switch socketConnectionType {
case .automatic:
socketConnectionHandler = AutomaticSocketConnectionHandler(socket: socket)
case .manual:
socketConnectionHandler = ManualSocketConnectionHandler(socket: socket)
}
let dispatcher = Dispatcher(socket: socket, socketConnectionHandler: socketConnectionHandler, logger: logger)
let relayUrlFactory = RelayUrlFactory(
relayHost: relayHost,
projectId: projectId,
socketAuthenticator: socketAuthenticator
)
let dispatcher = Dispatcher(
socketFactory: socketFactory,
relayUrlFactory: relayUrlFactory,
socketConnectionType: socketConnectionType,
logger: logger
)
self.init(dispatcher: dispatcher, logger: logger, keyValueStorage: keyValueStorage, clientIdStorage: clientIdStorage)
}

Expand Down
14 changes: 11 additions & 3 deletions Sources/WalletConnectRelay/RelayURLFactory.swift
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
import Foundation

struct RelayUrlFactory {
private let relayHost: String
private let projectId: String
private let socketAuthenticator: SocketAuthenticating

init(socketAuthenticator: SocketAuthenticating) {
init(
relayHost: String,
projectId: String,
socketAuthenticator: SocketAuthenticating
) {
self.relayHost = relayHost
self.projectId = projectId
self.socketAuthenticator = socketAuthenticator
}

func create(host: String, projectId: String) -> URL {
func create() -> URL {
var components = URLComponents()
components.scheme = "wss"
components.host = host
components.host = relayHost
components.queryItems = [
URLQueryItem(name: "projectId", value: projectId)
]
Expand Down
41 changes: 40 additions & 1 deletion Tests/RelayerTests/DispatcherTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import Combine
import TestingUtils
import Combine

private class DispatcherKeychainStorageMock: KeychainStorageProtocol {
func add<T>(_ item: T, forKey key: String) throws where T : WalletConnectKMS.GenericPasswordConvertible {}
func read<T>(key: String) throws -> T where T : WalletConnectKMS.GenericPasswordConvertible {
return try T(rawRepresentation: Data())
}
func delete(key: String) throws {}
func deleteAll() throws {}
}

class WebSocketMock: WebSocketConnecting {
var request: URLRequest = URLRequest(url: URL(string: "wss://relay.walletconnect.com")!)

Expand All @@ -29,15 +38,45 @@ class WebSocketMock: WebSocketConnecting {
}
}

class WebSocketFactoryMock: WebSocketFactory {
private let webSocket: WebSocketMock

init(webSocket: WebSocketMock) {
self.webSocket = webSocket
}

func create(with url: URL) -> WebSocketConnecting {
return webSocket
}
}

final class DispatcherTests: XCTestCase {
var publishers = Set<AnyCancellable>()
var sut: Dispatcher!
var webSocket: WebSocketMock!
var networkMonitor: NetworkMonitoringMock!

override func setUp() {
webSocket = WebSocketMock()
let webSocketFactory = WebSocketFactoryMock(webSocket: webSocket)
networkMonitor = NetworkMonitoringMock()
sut = Dispatcher(socket: webSocket, socketConnectionHandler: ManualSocketConnectionHandler(socket: webSocket), logger: ConsoleLoggerMock())
let keychainStorageMock = DispatcherKeychainStorageMock()
let clientIdStorage = ClientIdStorage(keychain: keychainStorageMock)
let socketAuthenticator = SocketAuthenticator(
clientIdStorage: clientIdStorage,
relayHost: "relay.walletconnect.com"
)
let relayUrlFactory = RelayUrlFactory(
relayHost: "relay.walletconnect.com",
projectId: "1012db890cf3cfb0c1cdc929add657ba",
socketAuthenticator: socketAuthenticator
)
sut = Dispatcher(
socketFactory: webSocketFactory,
relayUrlFactory: relayUrlFactory,
socketConnectionType: .manual,
logger: ConsoleLoggerMock()
)
}

func testSendWhileConnected() {
Expand Down