Skip to content

Commit

Permalink
Merge pull request #601 from WalletConnect/feature/socket-reconnectio…
Browse files Browse the repository at this point in the history
…n-#576

[Relay] Socket reconnection
  • Loading branch information
flypaper0 authored Nov 23, 2022
2 parents fb418f4 + 76329af commit df9bc93
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 32 deletions.
23 changes: 21 additions & 2 deletions Sources/WalletConnectRelay/AppStateObserving.swift
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import Foundation
#if os(iOS)

#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif

protocol AppStateObserving {
enum ApplicationState {
case background, foreground
}

protocol AppStateObserving: AnyObject {
var currentState: ApplicationState { get }
var onWillEnterForeground: (() -> Void)? {get set}
var onWillEnterBackground: (() -> Void)? {get set}
}

class AppStateObserver: AppStateObserving {

@objc var onWillEnterForeground: (() -> Void)?

@objc var onWillEnterBackground: (() -> Void)?
Expand All @@ -17,6 +26,16 @@ class AppStateObserver: AppStateObserving {
subscribeNotificationCenter()
}

var currentState: ApplicationState {
#if canImport(UIKit)
let isActive = UIApplication.shared.applicationState == .active
return isActive ? .foreground : .background
#elseif canImport(AppKit)
let isActive = NSApplication.shared.isActive
return isActive ? .foreground : .background
#endif
}

private func subscribeNotificationCenter() {
#if os(iOS)
NotificationCenter.default.addObserver(
Expand Down
1 change: 1 addition & 0 deletions Sources/WalletConnectRelay/Dispatching.swift
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ final class Dispatcher: NSObject, Dispatching {
}
socket.onDisconnect = { [unowned self] _ in
self.socketConnectionStatusPublisherSubject.send(.disconnected)
self.socketConnectionHandler.handleDisconnection()
}
}
}
2 changes: 1 addition & 1 deletion Sources/WalletConnectRelay/NetworkMonitoring.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Foundation
import Network

protocol NetworkMonitoring {
protocol NetworkMonitoring: AnyObject {
var onSatisfied: (() -> Void)? {get set}
var onUnsatisfied: (() -> Void)? {get set}
func startMonitoring()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,30 @@ import UIKit
import Foundation
import Combine

class AutomaticSocketConnectionHandler: SocketConnectionHandler {
enum Error: Swift.Error {
case manualSocketConnectionForbidden
case manualSocketDisconnectionForbidden
class AutomaticSocketConnectionHandler {

enum Errors: Error {
case manualSocketConnectionForbidden, manualSocketDisconnectionForbidden
}
private var appStateObserver: AppStateObserving
let socket: WebSocketConnecting
private var networkMonitor: NetworkMonitoring

private let socket: WebSocketConnecting
private let appStateObserver: AppStateObserving
private let networkMonitor: NetworkMonitoring
private let backgroundTaskRegistrar: BackgroundTaskRegistering

private var publishers = Set<AnyCancellable>()

init(networkMonitor: NetworkMonitoring = NetworkMonitor(),
socket: WebSocketConnecting,
appStateObserver: AppStateObserving = AppStateObserver(),
backgroundTaskRegistrar: BackgroundTaskRegistering = BackgroundTaskRegistrar()) {
init(
socket: WebSocketConnecting,
networkMonitor: NetworkMonitoring = NetworkMonitor(),
appStateObserver: AppStateObserving = AppStateObserver(),
backgroundTaskRegistrar: BackgroundTaskRegistering = BackgroundTaskRegistrar()
) {
self.appStateObserver = appStateObserver
self.socket = socket
self.networkMonitor = networkMonitor
self.backgroundTaskRegistrar = backgroundTaskRegistrar

setUpStateObserving()
setUpNetworkMonitoring()

Expand All @@ -36,40 +40,49 @@ class AutomaticSocketConnectionHandler: SocketConnectionHandler {
}

appStateObserver.onWillEnterForeground = { [unowned self] in
if !socket.isConnected {
socket.connect()
}
reconnectIfNeeded()
}
}

private func setUpNetworkMonitoring() {
networkMonitor.onSatisfied = { [weak self] in
self?.handleNetworkSatisfied()
self?.reconnectIfNeeded()
}
networkMonitor.startMonitoring()
}

func registerBackgroundTask() {
private func registerBackgroundTask() {
backgroundTaskRegistrar.register(name: "Finish Network Tasks") { [unowned self] in
endBackgroundTask()
}
}

func endBackgroundTask() {
private func endBackgroundTask() {
socket.disconnect()
}

private func reconnectIfNeeded() {
if !socket.isConnected {
socket.connect()
}
}
}

// MARK: - SocketConnectionHandler

extension AutomaticSocketConnectionHandler: SocketConnectionHandler {

func handleConnect() throws {
throw Error.manualSocketConnectionForbidden
throw Errors.manualSocketConnectionForbidden
}

func handleDisconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws {
throw Error.manualSocketDisconnectionForbidden
throw Errors.manualSocketDisconnectionForbidden
}

func handleNetworkSatisfied() {
if !socket.isConnected {
socket.connect()
func handleDisconnection() {
if appStateObserver.currentState == .foreground {
reconnectIfNeeded()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ class ManualSocketConnectionHandler: SocketConnectionHandler {
func handleDisconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws {
socket.disconnect()
}

func handleDisconnection() {
// No operation
// ManualSocketConnectionHandler does not support reconnection logic
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ import Foundation
protocol SocketConnectionHandler {
func handleConnect() throws
func handleDisconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws
func handleDisconnection()
}
31 changes: 24 additions & 7 deletions Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,26 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase {
var sut: AutomaticSocketConnectionHandler!
var webSocketSession: WebSocketMock!
var networkMonitor: NetworkMonitoringMock!
var appStateObserver: AppStateObserving!
var appStateObserver: AppStateObserverMock!
var backgroundTaskRegistrar: BackgroundTaskRegistrarMock!

override func setUp() {
webSocketSession = WebSocketMock()
networkMonitor = NetworkMonitoringMock()
appStateObserver = AppStateObserverMock()
backgroundTaskRegistrar = BackgroundTaskRegistrarMock()
sut = AutomaticSocketConnectionHandler(
networkMonitor: networkMonitor,
socket: webSocketSession,
networkMonitor: networkMonitor,
appStateObserver: appStateObserver,
backgroundTaskRegistrar: backgroundTaskRegistrar)
}

func testConnectsOnConnectionSatisfied() {
webSocketSession.disconnect()
XCTAssertFalse(sut.socket.isConnected)
XCTAssertFalse(webSocketSession.isConnected)
networkMonitor.onSatisfied?()
XCTAssertTrue(sut.socket.isConnected)
XCTAssertTrue(webSocketSession.isConnected)
}

func testHandleConnectThrows() {
Expand All @@ -38,7 +39,7 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase {
func testReconnectsOnEnterForeground() {
webSocketSession.disconnect()
appStateObserver.onWillEnterForeground?()
XCTAssertTrue(sut.socket.isConnected)
XCTAssertTrue(webSocketSession.isConnected)
}

func testRegisterTaskOnEnterBackground() {
Expand All @@ -49,8 +50,24 @@ final class AutomaticSocketConnectionHandlerTests: XCTestCase {

func testDisconnectOnEndBackgroundTask() {
appStateObserver.onWillEnterBackground?()
XCTAssertTrue(sut.socket.isConnected)
XCTAssertTrue(webSocketSession.isConnected)
backgroundTaskRegistrar.completion!()
XCTAssertFalse(sut.socket.isConnected)
XCTAssertFalse(webSocketSession.isConnected)
}

func testReconnectOnDisconnectForeground() {
appStateObserver.currentState = .foreground
XCTAssertTrue(webSocketSession.isConnected)
webSocketSession.disconnect()
sut.handleDisconnection()
XCTAssertTrue(webSocketSession.isConnected)
}

func testReconnectOnDisconnectBackground() {
appStateObserver.currentState = .background
XCTAssertTrue(webSocketSession.isConnected)
webSocketSession.disconnect()
sut.handleDisconnection()
XCTAssertFalse(webSocketSession.isConnected)
}
}
1 change: 1 addition & 0 deletions Tests/RelayerTests/Mocks/AppStateObserverMock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Foundation
@testable import WalletConnectRelay

class AppStateObserverMock: AppStateObserving {
var currentState: ApplicationState = .foreground
var onWillEnterForeground: (() -> Void)?
var onWillEnterBackground: (() -> Void)?
}

0 comments on commit df9bc93

Please sign in to comment.