diff --git a/Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift b/Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift index d05b249..a64b425 100644 --- a/Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift +++ b/Sources/NextcloudFileProviderKit/Enumeration/RemoteChangeObserver.swift @@ -45,9 +45,14 @@ public class RemoteChangeObserver: NSObject, NKCommonDelegate, URLSessionWebSock } public var pollingActive: Bool { pollingTimer != nil } - private var networkReachability: NKCommon.TypeReachability = .unknown { + private(set) var networkReachability: NKCommon.TypeReachability = .unknown { didSet { - if oldValue == .notReachable, networkReachability != .notReachable { + if networkReachability == .notReachable { + logger.info("Network unreachable, stopping websocket and stopping polling") + stopPollingTimer() + resetWebSocket() + } else if oldValue == .notReachable { + logger.info("Network reachable, trying to reconnect to websocket") reconnectWebSocket() changeNotificationInterface.notifyChange() } @@ -364,4 +369,8 @@ public class RemoteChangeObserver: NSObject, NKCommonDelegate, URLSessionWebSock ) } } + + public func networkReachabilityObserver(_ typeReachability: NKCommon.TypeReachability) { + networkReachability = typeReachability + } } diff --git a/Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift b/Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift index 8ada0a1..9d6e305 100644 --- a/Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift +++ b/Tests/NextcloudFileProviderKitTests/RemoteChangeObserverTests.swift @@ -333,4 +333,87 @@ final class RemoteChangeObserverTests: XCTestCase { try await Task.sleep(nanoseconds: UInt64(intendedPingsWait)) XCTAssertEqual(pings, intendedPings) } + + func testRetryOnConnectionLoss() async throws { + let remoteInterface = MockRemoteInterface(account: Self.account) + remoteInterface.capabilities = mockCapabilities + + var authenticated = false + var notified = false + + NotificationCenter.default.addObserver( + forName: NotifyPushAuthenticatedNotificationName, object: nil, queue: nil + ) { _ in + authenticated = true + } + + let notificationInterface = MockChangeNotificationInterface() + notificationInterface.changeHandler = { notified = true } + remoteChangeObserver = RemoteChangeObserver( + remoteInterface: remoteInterface, + changeNotificationInterface: notificationInterface, + domain: nil + ) + remoteChangeObserver?.networkReachabilityObserver(.reachableEthernetOrWiFi) + + for _ in 0...Self.timeout { + try await Task.sleep(nanoseconds: 1_000_000) + if authenticated { + break + } + } + XCTAssertTrue(authenticated) + + Self.notifyPushServer.send(message: "notify_file") + for _ in 0...Self.timeout { + try await Task.sleep(nanoseconds: 1_000_000) + if notified { + break + } + } + XCTAssertTrue(notified) // Check notification handling is working properly + + remoteChangeObserver?.networkReachabilityObserver(.notReachable) + Self.notifyPushServer.resetCredentialsState() + authenticated = false + for _ in 0...Self.timeout { + try await Task.sleep(nanoseconds: 1_000_000) + if authenticated { + break + } + } + // Should still be false. The mock notify push server is still online so if we the + // remote change observer attempts to connect it _will_ be correctly authentiated, + // but once we have set the network reachability to unreachable it shouldn't be + // trying to connect at all. + XCTAssertFalse(authenticated) + + notified = false + Self.notifyPushServer.send(message: "notify_file") + for _ in 0...Self.timeout { + try await Task.sleep(nanoseconds: 1_000_000) + if notified { + break + } + } + XCTAssertFalse(notified) // Check we disconnected and are not listening to the server + + remoteChangeObserver?.networkReachabilityObserver(.reachableEthernetOrWiFi) + for _ in 0...Self.timeout { + try await Task.sleep(nanoseconds: 1_000_000) + if authenticated { + break + } + } + XCTAssertTrue(authenticated) + + Self.notifyPushServer.send(message: "notify_file") + for _ in 0...Self.timeout { + try await Task.sleep(nanoseconds: 1_000_000) + if notified { + break + } + } + XCTAssertTrue(notified) // Check notification handling is working properly again + } }