refactor: node tools and canvas host url

This commit is contained in:
Peter Steinberger
2025-12-27 01:36:24 +01:00
parent 52ca5c4aa2
commit c54e4d0900
19 changed files with 448 additions and 128 deletions

View File

@@ -0,0 +1,22 @@
import Foundation
enum AsyncTimeout {
static func withTimeout<T: Sendable>(
seconds: Double,
onTimeout: @escaping @Sendable () -> Error,
operation: @escaping @Sendable () async throws -> T) async throws -> T
{
let clamped = max(0, seconds)
return try await withThrowingTaskGroup(of: T.self) { group in
group.addTask { try await operation() }
group.addTask {
try await Task.sleep(nanoseconds: UInt64(clamped * 1_000_000_000))
throw onTimeout()
}
let result = try await group.next()
group.cancelAll()
if let result { return result }
throw onTimeout()
}
}
}

View File

@@ -17,14 +17,22 @@ actor MacNodeBridgePairingClient {
let connection = NWConnection(to: endpoint, using: .tcp)
let queue = DispatchQueue(label: "com.steipete.clawdis.macos.bridge-client")
defer { connection.cancel() }
try await self.withTimeout(seconds: 8, purpose: "connect") {
try await AsyncTimeout.withTimeout(seconds: 8, onTimeout: {
NSError(domain: "Bridge", code: 0, userInfo: [
NSLocalizedDescriptionKey: "connect timed out",
])
}) {
try await self.startAndWaitForReady(connection, queue: queue)
}
onStatus?("Authenticating…")
try await self.send(hello, over: connection)
let first = try await self.withTimeout(seconds: 10, purpose: "hello") { () -> ReceivedFrame in
let first = try await AsyncTimeout.withTimeout(seconds: 10, onTimeout: {
NSError(domain: "Bridge", code: 0, userInfo: [
NSLocalizedDescriptionKey: "hello timed out",
])
}) { () -> ReceivedFrame in
guard let frame = try await self.receiveFrame(over: connection) else {
throw NSError(domain: "Bridge", code: 0, userInfo: [
NSLocalizedDescriptionKey: "Bridge closed connection during hello",
@@ -60,7 +68,11 @@ actor MacNodeBridgePairingClient {
over: connection)
onStatus?("Waiting for approval…")
let ok = try await self.withTimeout(seconds: 60, purpose: "pairing approval") {
let ok = try await AsyncTimeout.withTimeout(seconds: 60, onTimeout: {
NSError(domain: "Bridge", code: 0, userInfo: [
NSLocalizedDescriptionKey: "pairing approval timed out",
])
}) {
while let next = try await self.receiveFrame(over: connection) {
switch next.base.type {
case "pair-ok":
@@ -172,25 +184,5 @@ actor MacNodeBridgePairingClient {
}
}
private func withTimeout<T: Sendable>(
seconds: Double,
purpose: String,
operation: @escaping @Sendable () async throws -> T) async throws -> T
{
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask { try await operation() }
group.addTask {
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
throw NSError(domain: "Bridge", code: 0, userInfo: [
NSLocalizedDescriptionKey: "\(purpose) timed out",
])
}
let result = try await group.next()
group.cancelAll()
if let result { return result }
throw NSError(domain: "Bridge", code: 0, userInfo: [
NSLocalizedDescriptionKey: "\(purpose) timed out",
])
}
}
}

View File

@@ -48,11 +48,15 @@ actor MacNodeBridgeSession {
try await Self.waitForReady(stateStream, timeoutSeconds: 6)
try await Self.withTimeout(seconds: 6) {
try await AsyncTimeout.withTimeout(seconds: 6, onTimeout: {
TimeoutError(message: "operation timed out")
}) {
try await self.send(hello)
}
guard let line = try await Self.withTimeout(seconds: 6, operation: {
guard let line = try await AsyncTimeout.withTimeout(seconds: 6, onTimeout: {
TimeoutError(message: "operation timed out")
}, operation: {
try await self.receiveLine()
}),
let data = line.data(using: .utf8),
@@ -290,7 +294,9 @@ actor MacNodeBridgeSession {
_ stream: AsyncStream<NWConnection.State>,
timeoutSeconds: Double) async throws
{
try await self.withTimeout(seconds: timeoutSeconds) {
try await AsyncTimeout.withTimeout(seconds: timeoutSeconds, onTimeout: {
TimeoutError(message: "operation timed out")
}) {
for await state in stream {
switch state {
case .ready:
@@ -311,22 +317,5 @@ actor MacNodeBridgeSession {
}
}
private static func withTimeout<T: Sendable>(
seconds: Double,
operation: @escaping @Sendable () async throws -> T) async throws -> T
{
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask {
try await operation()
}
group.addTask {
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
throw TimeoutError(message: "operation timed out")
}
let result = try await group.next()
group.cancelAll()
if let result { return result }
throw TimeoutError(message: "operation timed out")
}
}
}

View File

@@ -228,7 +228,11 @@ final class MacNodeModeCoordinator {
_ stream: AsyncStream<NWConnection.State>,
timeoutSeconds: Double) async throws
{
try await self.withTimeout(seconds: timeoutSeconds) {
try await AsyncTimeout.withTimeout(seconds: timeoutSeconds, onTimeout: {
NSError(domain: "Bridge", code: 22, userInfo: [
NSLocalizedDescriptionKey: "operation timed out",
])
}) {
for await state in stream {
switch state {
case .ready:
@@ -249,27 +253,6 @@ final class MacNodeModeCoordinator {
}
}
private static func withTimeout<T: Sendable>(
seconds: Double,
operation: @escaping @Sendable () async throws -> T) async throws -> T
{
try await withThrowingTaskGroup(of: T.self) { group in
group.addTask { try await operation() }
group.addTask {
try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
throw NSError(domain: "Bridge", code: 22, userInfo: [
NSLocalizedDescriptionKey: "operation timed out",
])
}
let result = try await group.next()
group.cancelAll()
if let result { return result }
throw NSError(domain: "Bridge", code: 22, userInfo: [
NSLocalizedDescriptionKey: "operation timed out",
])
}
}
private func resolveBridgeEndpoint(timeoutSeconds: Double) async -> NWEndpoint? {
let mode = await MainActor.run(body: { AppStateStore.shared.connectionMode })
if mode == .remote {