Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: use the deployment's hostname suffix in the UI#133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ethanndickson merged 10 commits intomainfromethan/deployment-hostname-suffix
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes fromall 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
7 changes: 6 additions & 1 deletionCoder-Desktop/Coder-Desktop/Coder_DesktopApp.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -41,10 +41,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {

override init() {
vpn = CoderVPNService()
state = AppState(onChange: vpn.configureTunnelProviderProtocol)
let state = AppState(onChange: vpn.configureTunnelProviderProtocol)
vpn.onStart = {
// We don't need this to have finished before the VPN actually starts
Task { await state.refreshDeploymentConfig() }
}
if state.startVPNOnLaunch {
vpn.startWhenReady = true
}
self.state = state
vpn.installSystemExtension()
#if arch(arm64)
let mutagenBinary = "mutagen-darwin-arm64"
Expand Down
51 changes: 48 additions & 3 deletionsCoder-Desktop/Coder-Desktop/State.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,10 @@ class AppState: ObservableObject {
}
}

@Published private(set) var hostnameSuffix: String = defaultHostnameSuffix

static let defaultHostnameSuffix: String = "coder"

// Stored in Keychain
@Published private(set) var sessionToken: String? {
didSet {
Expand All@@ -33,6 +37,8 @@ class AppState: ObservableObject {
}
}

private var client: Client?

@Published var useLiteralHeaders: Bool = UserDefaults.standard.bool(forKey: Keys.useLiteralHeaders) {
didSet {
reconfigure()
Expand DownExpand Up@@ -80,7 +86,7 @@ class AppState: ObservableObject {
private let keychain: Keychain
private let persistent: Bool

let onChange: ((NETunnelProviderProtocol?) -> Void)?
privatelet onChange: ((NETunnelProviderProtocol?) -> Void)?

// reconfigure must be called when any property used to configure the VPN changes
public func reconfigure() {
Expand All@@ -107,21 +113,35 @@ class AppState: ObservableObject {
if sessionToken == nil || sessionToken!.isEmpty == true {
clearSession()
}
client = Client(
url: baseAccessURL!,
token: sessionToken!,
headers: useLiteralHeaders ? literalHeaders.map { $0.toSDKHeader() } : []
)
Task {
await handleTokenExpiry()
Copy link
MemberAuthor

@ethanndicksonethanndicksonApr 11, 2025
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

drive-by fix: we need to check for token expiry on app launch. We were previously only checking when the menu bar window was opened, and Connect was disabled. We need to account for when Connect is configured to start when the app is launched.

await refreshDeploymentConfig()
}
}
}

public func login(baseAccessURL: URL, sessionToken: String) {
hasSession = true
self.baseAccessURL = baseAccessURL
self.sessionToken = sessionToken
client = Client(
url: baseAccessURL,
token: sessionToken,
headers: useLiteralHeaders ? literalHeaders.map { $0.toSDKHeader() } : []
)
Task { await refreshDeploymentConfig() }
reconfigure()
}

public func handleTokenExpiry() async {
if hasSession {
let client = Client(url: baseAccessURL!, token: sessionToken!)
do {
_ = try await client.user("me")
_ = try await client!.user("me")
} catch let SDKError.api(apiErr) {
// Expired token
if apiErr.statusCode == 401 {
Expand All@@ -135,9 +155,34 @@ class AppState: ObservableObject {
}
}

private var refreshTask: Task<String?, Never>?
public func refreshDeploymentConfig() async {
// Client is non-nil if there's a sesssion
if hasSession, let client {
refreshTask?.cancel()

refreshTask = Task {
let res = try? await retry(floor: .milliseconds(100), ceil: .seconds(10)) {
do {
let config = try await client.agentConnectionInfoGeneric()
return config.hostname_suffix
} catch {
logger.error("failed to get agent connection info (retrying): \(error)")
throw error
}
}
return res
}

hostnameSuffix = await refreshTask?.value ?? Self.defaultHostnameSuffix
}
}

public func clearSession() {
hasSession = false
sessionToken = nil
refreshTask?.cancel()
client = nil
reconfigure()
}

Expand Down
6 changes: 5 additions & 1 deletionCoder-Desktop/Coder-Desktop/VPN/VPNService.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -76,6 +76,7 @@ final class CoderVPNService: NSObject, VPNService {

// Whether the VPN should start as soon as possible
var startWhenReady: Bool = false
var onStart: (() -> Void)?

// systemExtnDelegate holds a reference to the SystemExtensionDelegate so that it doesn't get
// garbage collected while the OSSystemExtensionRequest is in flight, since the OS framework
Expand DownExpand Up@@ -187,8 +188,11 @@ extension CoderVPNService {
xpc.connect()
xpc.ping()
tunnelState = .connecting
// Non-connected -> Connected: Retrieve Peers
// Non-connected -> Connected:
// - Retrieve Peers
// - Run `onStart` closure
case (_, .connected):
onStart?()
xpc.connect()
xpc.getPeerState()
tunnelState = .connected
Expand Down
9 changes: 6 additions & 3 deletionsCoder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -42,20 +42,23 @@ enum VPNMenuItem: Equatable, Comparable, Identifiable {
}

struct MenuItemView: View {
@EnvironmentObject var state: AppState

let item: VPNMenuItem
let baseAccessURL: URL
@State private var nameIsSelected: Bool = false
@State private var copyIsSelected: Bool = false

private var itemName: AttributedString {
let name = switch item {
case let .agent(agent): agent.primaryHost ?? "\(item.wsName).coder"
case .offlineWorkspace: "\(item.wsName).coder"
case let .agent(agent): agent.primaryHost ?? "\(item.wsName).\(state.hostnameSuffix)"
case .offlineWorkspace: "\(item.wsName).\(state.hostnameSuffix)"
}

var formattedName = AttributedString(name)
formattedName.foregroundColor = .primary
if let range = formattedName.range(of: ".coder") {

if let range = formattedName.range(of: ".\(state.hostnameSuffix)", options: .backwards) {
formattedName[range].foregroundColor = .secondary
}
return formattedName
Expand Down
25 changes: 25 additions & 0 deletionsCoder-Desktop/CoderSDK/Util.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
import Foundation

public func retry<T>(
floor: Duration,
ceil: Duration,
rate: Double = 1.618,
operation: @Sendable () async throws -> T
) async throws -> T {
var delay = floor

while !Task.isCancelled {
do {
return try await operation()
} catch let error as CancellationError {
throw error
} catch {
try Task.checkCancellation()

delay = min(ceil, delay * rate)
try await Task.sleep(for: delay)
}
}

throw CancellationError()
}
15 changes: 15 additions & 0 deletionsCoder-Desktop/CoderSDK/WorkspaceAgents.swift
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
import Foundation

public extension Client {
func agentConnectionInfoGeneric() async throws(SDKError) -> AgentConnectionInfo {
let res = try await request("/api/v2/workspaceagents/connection", method: .get)
guard res.resp.statusCode == 200 else {
throw responseAsError(res)
}
return try decode(AgentConnectionInfo.self, from: res.data)
}
}

public struct AgentConnectionInfo: Codable, Sendable {
public let hostname_suffix: String?
}
Loading

[8]ページ先頭

©2009-2025 Movatter.jp