Reconnect at once when the Mac wakes or its network changes

This commit is contained in:
Ian Gulliver
2026-10-23 07:09:37 -07:00
parent 5b69d2c639
commit c28b100fd0
3 changed files with 79 additions and 19 deletions
+7 -18
View File
@@ -1,5 +1,4 @@
import Foundation
import Network
import SwiftUI
private let staleAfter: TimeInterval = 40
@@ -26,8 +25,7 @@ final class EventStream {
private var retry: Task<Void, Never>?
private var checker: Task<Void, Never>?
private var generation = 0
private let monitor = NWPathMonitor()
private var online: Bool?
private var networkObserver: (any NSObjectProtocol)?
private var activeObserver: NotificationCenter.ObservationToken?
init(api: API, after: Int64, lead: Bool, onEvent: @escaping (ByreEvent) -> Void, onState: @escaping (Bool) -> Void, onReady: @escaping () -> Void) {
@@ -43,13 +41,9 @@ final class EventStream {
self?.check()
}
}
monitor.pathUpdateHandler = { [weak self] path in
let up = path.status == .satisfied
Task { @MainActor in
self?.pathChanged(up)
}
networkObserver = NetworkWatch.shared.observe { [weak self] in
self?.connect()
}
monitor.start(queue: .main)
#if os(macOS)
activeObserver = NotificationCenter.default.addObserver(of: NSApplication.self, for: .didBecomeActive) { [weak self] _ in
self?.check()
@@ -67,21 +61,16 @@ final class EventStream {
checker?.cancel()
retry?.cancel()
reading?.cancel()
monitor.cancel()
if let networkObserver {
NotificationCenter.default.removeObserver(networkObserver)
}
networkObserver = nil
if let activeObserver {
NotificationCenter.default.removeObserver(activeObserver)
}
activeObserver = nil
}
private func pathChanged(_ up: Bool) {
let was = online
online = up
if was == false && up {
connect()
}
}
private func check() {
if Date().timeIntervalSince(heard) < staleAfter {
return
+54
View File
@@ -0,0 +1,54 @@
import Foundation
import Network
#if os(macOS)
import AppKit
#endif
private let networkChanged = Notification.Name("run.fc.byre.networkChanged")
@MainActor
final class NetworkWatch {
static let shared = NetworkWatch()
private let monitor = NWPathMonitor()
private var seen = false
private init() {
monitor.pathUpdateHandler = { [weak self] path in
let summary = "\(path.status) \(path.availableInterfaces.map(\.name).joined(separator: " "))"
Task { @MainActor in
self?.pathChanged(summary)
}
}
monitor.start(queue: .main)
#if os(macOS)
NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didWakeNotification, object: nil, queue: .main) { [weak self] _ in
Task { @MainActor in
log.notice("woke from sleep, reconnecting")
self?.post()
}
}
#endif
}
func observe(_ handler: @escaping @MainActor () -> Void) -> any NSObjectProtocol {
NotificationCenter.default.addObserver(forName: networkChanged, object: self, queue: .main) { _ in
MainActor.assumeIsolated {
handler()
}
}
}
private func pathChanged(_ summary: String) {
guard seen else {
seen = true
return
}
log.notice("network changed (\(summary, privacy: .public)), reconnecting")
post()
}
private func post() {
NotificationCenter.default.post(name: networkChanged, object: self)
}
}
+18 -1
View File
@@ -38,6 +38,7 @@ final class Remote {
private var stopped = false
private var heard = Date()
private var checker: Task<Void, Never>?
private var networkObserver: (any NSObjectProtocol)?
init(api: API) {
self.api = api
@@ -69,16 +70,30 @@ final class Remote {
self?.check()
}
}
networkObserver = NetworkWatch.shared.observe { [weak self] in
self?.restart()
}
}
}
func stop() {
stopped = true
checker?.cancel()
if let networkObserver {
NotificationCenter.default.removeObserver(networkObserver)
}
networkObserver = nil
ws?.cancel(with: .normalClosure, reason: nil)
ws = nil
}
private func restart() {
let old = ws
ws = nil
old?.cancel(with: .goingAway, reason: nil)
start()
}
// A socket byre closed while this Mac slept stays open here until something is sent on it.
private func check() {
guard let task = ws else {
@@ -122,7 +137,9 @@ final class Remote {
log.notice("client connection closed:\(errorText(error), privacy: .public)")
self.ws = nil
try? await Task.sleep(for: retryAfter)
self.start()
if self.ws == nil {
self.start()
}
}
}
}