Reconnect the app's command channel after sleep; list clients from it
This commit is contained in:
@@ -95,6 +95,8 @@ func errorText(_ error: any Error) -> String {
|
||||
return String(describing: e)
|
||||
}
|
||||
|
||||
private let clientID = UUID().uuidString
|
||||
|
||||
final class API: Sendable {
|
||||
let origin: URL
|
||||
let session: URLSession
|
||||
@@ -104,6 +106,7 @@ final class API: Sendable {
|
||||
self.origin = origin
|
||||
let config = URLSessionConfiguration.default
|
||||
config.httpAdditionalHeaders = [
|
||||
"Byre-Client": clientID,
|
||||
"Byre-Commit": Bundle.main.object(forInfoDictionaryKey: "ByreCommit") as! String,
|
||||
"Byre-Host": Host.current().localizedName ?? ProcessInfo.processInfo.hostName,
|
||||
]
|
||||
|
||||
@@ -7,6 +7,8 @@ typealias ClientRequest = Components.Schemas.ClientRequest
|
||||
typealias ClientReply = Components.Schemas.ClientReply
|
||||
|
||||
private let retryAfter = Duration.seconds(2)
|
||||
private let checkEvery = Duration.seconds(10)
|
||||
private let staleAfter: TimeInterval = 25
|
||||
private let maxOutput = 64 << 10
|
||||
private let maxMessage = 256 << 20
|
||||
private let maxRead = 64 << 20
|
||||
@@ -18,6 +20,8 @@ final class Remote {
|
||||
private let hands = Hands()
|
||||
private var ws: URLSessionWebSocketTask?
|
||||
private var stopped = false
|
||||
private var heard = Date()
|
||||
private var checker: Task<Void, Never>?
|
||||
|
||||
init(api: API) {
|
||||
self.api = api
|
||||
@@ -30,12 +34,22 @@ final class Remote {
|
||||
let task = api.session.webSocketTask(with: api.url("/api/clients/connect", scheme: "wss"))
|
||||
task.maximumMessageSize = maxMessage
|
||||
ws = task
|
||||
heard = Date()
|
||||
task.resume()
|
||||
receive(task)
|
||||
if checker == nil {
|
||||
checker = Task { [weak self] in
|
||||
while !Task.isCancelled {
|
||||
try? await Task.sleep(for: checkEvery)
|
||||
self?.check()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stop() {
|
||||
stopped = true
|
||||
checker?.cancel()
|
||||
ws?.cancel(with: .normalClosure, reason: nil)
|
||||
ws = nil
|
||||
Task { [hands] in
|
||||
@@ -43,6 +57,30 @@ final class Remote {
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
return
|
||||
}
|
||||
if Date().timeIntervalSince(heard) >= staleAfter {
|
||||
log.notice("client connection got no pong for \(Int(staleAfter))s, reconnecting")
|
||||
ws = nil
|
||||
task.cancel(with: .goingAway, reason: nil)
|
||||
start()
|
||||
return
|
||||
}
|
||||
task.sendPing { error in
|
||||
guard error == nil else {
|
||||
return
|
||||
}
|
||||
Task { @MainActor in
|
||||
if self.ws === task {
|
||||
self.heard = Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func receive(_ task: URLSessionWebSocketTask) {
|
||||
task.receive { result in
|
||||
Task { @MainActor in
|
||||
@@ -59,7 +97,7 @@ final class Remote {
|
||||
case .success:
|
||||
self.receive(task)
|
||||
case .failure(let error):
|
||||
log.info("client connection closed: \(errorText(error), privacy: .public)")
|
||||
log.notice("client connection closed:\(errorText(error), privacy: .public)")
|
||||
self.ws = nil
|
||||
try? await Task.sleep(for: retryAfter)
|
||||
self.start()
|
||||
|
||||
Reference in New Issue
Block a user