- Notifications
You must be signed in to change notification settings - Fork3
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
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
6462177
f141a74
5dea75f
e04f61a
cbd8ce4
a354ebe
75f17ca
ba2d732
26d2eb8
b6218fc
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 { | ||
@@ -33,6 +37,8 @@ class AppState: ObservableObject { | ||
} | ||
} | ||
private var client: Client? | ||
@Published var useLiteralHeaders: Bool = UserDefaults.standard.bool(forKey: Keys.useLiteralHeaders) { | ||
didSet { | ||
reconfigure() | ||
@@ -80,7 +86,7 @@ class AppState: ObservableObject { | ||
private let keychain: Keychain | ||
private let persistent: Bool | ||
privatelet onChange: ((NETunnelProviderProtocol?) -> Void)? | ||
// reconfigure must be called when any property used to configure the VPN changes | ||
public func reconfigure() { | ||
@@ -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() | ||
MemberAuthor
| ||
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 { | ||
do { | ||
_ = try await client!.user("me") | ||
} catch let SDKError.api(apiErr) { | ||
// Expired token | ||
if apiErr.statusCode == 401 { | ||
@@ -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() | ||
} | ||
Original file line number | Diff line number | Diff 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() | ||
} |
Original file line number | Diff line number | Diff 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? | ||
} |
Uh oh!
There was an error while loading.Please reload this page.