Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -239,27 +239,13 @@ final class CloudService {
// The app server link: this Mac's `cmux link`, or with `CMUX_NEXT_CLOUD_NATIVE_LINK=1` the Native
// device route (#28947) with `cmux link` as its fallback when Native does not start.
let linkBuild = LaunchIdentity.current().build
let cmuxLink = CloudLinkLauncher.forApp(build: linkBuild, relay: relay)
let launcher: (any CloudLinkLaunching)?
if configuration.linkSource != .appServer {
launcher = nil
linkRoute = { "legacy" }
} else if CloudNativeFirstLauncher.choice() == .link {
launcher = cmuxLink
linkRoute = { "link" }
} else if let native = CloudNativeRouteHolder.forApp(identity: identity, auth: auth, paths: paths,
build: linkBuild, deviceName: Self.deviceName) {
let logger = Logger(subsystem: "com.cmuxterm.app.next", category: "app.cloud")
let first = CloudNativeFirstLauncher(native: native, link: cmuxLink) { error in
logger.error("cloud.link native route failed, using cmux link: \(String(describing: error), privacy: .public)")
}
launcher = first
linkRoute = { first.route }
} else {
launcher = cmuxLink
linkRoute = { "link (native asked; no Native route holder for this app)" }
let appServerLink = CloudAppServerLink(appServer: configuration.linkSource == .appServer, link: CloudLinkLauncher.forApp(build: linkBuild, relay: relay),
native: { CloudNativeRouteHolder.forApp(identity: identity, auth: auth, paths: paths, build: linkBuild, deviceName: Self.deviceName) }) { error in
Logger(subsystem: "com.cmuxterm.app.next", category: "app.cloud")
.error("cloud.link native route failed, using cmux link: \(String(describing: error), privacy: .public)")
}
link = CloudLinkLifecycle(launcher: launcher, installID: { [identity] in await identity.installID() },
linkRoute = appServerLink.route
link = CloudLinkLifecycle(launcher: appServerLink.launcher, installID: { [identity] in await identity.installID() },
connection: { [machines] in machines.local.connection })
binary = try? DaemonLauncher.resolveBinary(bundle: .main, environment: ProcessInfo.processInfo.environment)
move.cli = binary
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
public import CmuxNextDaemon

/// The app server link's launcher, and its route as launched for `cloud.machines`' `link_route`: `legacy`,
/// `link`, or the Native-first launcher's (``CloudNativeFirstLauncher/route``).
@MainActor
public struct CloudAppServerLink {
/// Nil for the legacy link (no app server link).
public let launcher: (any CloudLinkLaunching)?
public let route: @MainActor () -> String

/// The launcher of an `appServer` link source: this Mac's `cmux link` (`link`), or with `choice` `.native`
/// (`CMUX_NEXT_CLOUD_NATIVE_LINK=1`, #28947) the Native device route `native()` makes, with `link` as its
/// fallback when Native does not start (`onFallback` says why). `native` runs only for that choice.
public init(appServer: Bool, link: (any CloudLinkLaunching)?,
choice: CloudNativeFirstLauncher.Choice = CloudNativeFirstLauncher.choice(),
native: () -> (any CloudLinkLaunching)?, onFallback: @escaping (any Error) -> Void) {
if !appServer {
(launcher, route) = (nil, { "legacy" })
} else if choice == .link {
(launcher, route) = (link, { "link" })
} else if let native = native() {
let first = CloudNativeFirstLauncher(native: native, link: link, onFallback: onFallback)
(launcher, route) = (first, { first.route })
} else {
(launcher, route) = (link, { "link (native asked; no Native route holder for this app)" })
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,28 @@ struct CloudNativeFirstLauncherTests {
#expect(try await launcher.ensure(install: "i").status == "native")
}

/// `cloud.machines`' `link_route` (#32494): the launcher each link source and choice gets, and its route.
@Test func theAppServerLinkReportsTheRouteItLaunched() async throws {
let link = FakeLauncher(status: "link")
var asked = 0
let legacy = CloudAppServerLink(appServer: false, link: link, choice: .native, native: { asked += 1; return nil }) { _ in }
#expect(legacy.launcher == nil && legacy.route() == "legacy")
let cmuxLink = CloudAppServerLink(appServer: true, link: link, choice: .link, native: { asked += 1; return nil }) { _ in }
#expect(cmuxLink.launcher as? FakeLauncher === link && cmuxLink.route() == "link")
#expect(asked == 0, "the Native route holder was made for a choice that does not use it")
let noHolder = CloudAppServerLink(appServer: true, link: link, choice: .native, native: { asked += 1; return nil }) { _ in }
#expect(asked == 1 && noHolder.launcher as? FakeLauncher === link)
#expect(noHolder.route() == "link (native asked; no Native route holder for this app)")

let native = FakeLauncher(status: "native", failure: NativeDeviceEnrollmentError.refused(400))
var reasons = 0
let first = CloudAppServerLink(appServer: true, link: link, choice: .native, native: { native }) { _ in reasons += 1 }
#expect(first.launcher is CloudNativeFirstLauncher && first.route() == "native")
#expect(try await first.launcher?.ensure(install: "i").status == "link")
#expect(reasons == 1)
#expect(first.route() == "link (native failed: \(NativeDeviceEnrollmentError.refused(400)))")
}

@Test func signedOutIsNotARouteFailure() async {
let native = FakeLauncher(status: "native", failure: NativeDeviceEnrollmentError.signedOut)
let link = FakeLauncher(status: "link")
Expand Down
Loading