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

Commitafd9634

Browse files
feat: use the deployment's hostname suffix in the UI (#133)
Closes#93.<img width="272" alt="image" src="https://github.com/user-attachments/assets/54786bea-9d32-432f-9869-5abd42a86516" />The only time the hostname suffix is used by the desktop app is when an offline workspace needs to be shown in the list, where we naively append `.coder`. This PR sets this appended value to whatever `--workspace-hostname-suffix` is configured to deployment-side.We read the config value from the deployment when:- The app is launched, if the user is signed in.- The user signs in.- The VPN is started.
1 parent918bacd commitafd9634

File tree

6 files changed

+105
-8
lines changed

6 files changed

+105
-8
lines changed

‎Coder-Desktop/Coder-Desktop/Coder_DesktopApp.swift

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,15 @@ class AppDelegate: NSObject, NSApplicationDelegate {
4141

4242
overrideinit(){
4343
vpn=CoderVPNService()
44-
state=AppState(onChange: vpn.configureTunnelProviderProtocol)
44+
letstate=AppState(onChange: vpn.configureTunnelProviderProtocol)
45+
vpn.onStart={
46+
// We don't need this to have finished before the VPN actually starts
47+
Task{await state.refreshDeploymentConfig()}
48+
}
4549
if state.startVPNOnLaunch{
4650
vpn.startWhenReady=true
4751
}
52+
self.state= state
4853
vpn.installSystemExtension()
4954
#if arch(arm64)
5055
letmutagenBinary="mutagen-darwin-arm64"

‎Coder-Desktop/Coder-Desktop/State.swift

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,10 @@ class AppState: ObservableObject {
2525
}
2626
}
2727

28+
@Publishedprivate(set)varhostnameSuffix:String= defaultHostnameSuffix
29+
30+
staticletdefaultHostnameSuffix:String="coder"
31+
2832
// Stored in Keychain
2933
@Publishedprivate(set)varsessionToken:String?{
3034
didSet{
@@ -33,6 +37,8 @@ class AppState: ObservableObject {
3337
}
3438
}
3539

40+
privatevarclient:Client?
41+
3642
@PublishedvaruseLiteralHeaders:Bool=UserDefaults.standard.bool(forKey:Keys.useLiteralHeaders){
3743
didSet{
3844
reconfigure()
@@ -80,7 +86,7 @@ class AppState: ObservableObject {
8086
privateletkeychain:Keychain
8187
privateletpersistent:Bool
8288

83-
letonChange:((NETunnelProviderProtocol?)->Void)?
89+
privateletonChange:((NETunnelProviderProtocol?)->Void)?
8490

8591
// reconfigure must be called when any property used to configure the VPN changes
8692
publicfunc reconfigure(){
@@ -107,21 +113,35 @@ class AppState: ObservableObject {
107113
if sessionToken==nil || sessionToken!.isEmpty==true{
108114
clearSession()
109115
}
116+
client=Client(
117+
url: baseAccessURL!,
118+
token: sessionToken!,
119+
headers: useLiteralHeaders? literalHeaders.map{ $0.toSDKHeader()}:[]
120+
)
121+
Task{
122+
awaithandleTokenExpiry()
123+
awaitrefreshDeploymentConfig()
124+
}
110125
}
111126
}
112127

113128
publicfunc login(baseAccessURL:URL, sessionToken:String){
114129
hasSession=true
115130
self.baseAccessURL= baseAccessURL
116131
self.sessionToken= sessionToken
132+
client=Client(
133+
url: baseAccessURL,
134+
token: sessionToken,
135+
headers: useLiteralHeaders? literalHeaders.map{ $0.toSDKHeader()}:[]
136+
)
137+
Task{awaitrefreshDeploymentConfig()}
117138
reconfigure()
118139
}
119140

120141
publicfunc handleTokenExpiry()async{
121142
if hasSession{
122-
letclient=Client(url: baseAccessURL!, token: sessionToken!)
123143
do{
124-
_=tryawait client.user("me")
144+
_=tryawait client!.user("me")
125145
}catchletSDKError.api(apiErr){
126146
// Expired token
127147
if apiErr.statusCode==401{
@@ -135,9 +155,34 @@ class AppState: ObservableObject {
135155
}
136156
}
137157

158+
privatevarrefreshTask:Task<String?,Never>?
159+
publicfunc refreshDeploymentConfig()async{
160+
// Client is non-nil if there's a sesssion
161+
if hasSession,let client{
162+
refreshTask?.cancel()
163+
164+
refreshTask=Task{
165+
letres=try?awaitretry(floor:.milliseconds(100), ceil:.seconds(10)){
166+
do{
167+
letconfig=tryawait client.agentConnectionInfoGeneric()
168+
return config.hostname_suffix
169+
}catch{
170+
logger.error("failed to get agent connection info (retrying):\(error)")
171+
throw error
172+
}
173+
}
174+
return res
175+
}
176+
177+
hostnameSuffix=await refreshTask?.value??Self.defaultHostnameSuffix
178+
}
179+
}
180+
138181
publicfunc clearSession(){
139182
hasSession=false
140183
sessionToken=nil
184+
refreshTask?.cancel()
185+
client=nil
141186
reconfigure()
142187
}
143188

‎Coder-Desktop/Coder-Desktop/VPN/VPNService.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ final class CoderVPNService: NSObject, VPNService {
7676

7777
// Whether the VPN should start as soon as possible
7878
varstartWhenReady:Bool=false
79+
varonStart:(()->Void)?
7980

8081
// systemExtnDelegate holds a reference to the SystemExtensionDelegate so that it doesn't get
8182
// garbage collected while the OSSystemExtensionRequest is in flight, since the OS framework
@@ -187,8 +188,11 @@ extension CoderVPNService {
187188
xpc.connect()
188189
xpc.ping()
189190
tunnelState=.connecting
190-
// Non-connected -> Connected: Retrieve Peers
191+
// Non-connected -> Connected:
192+
// - Retrieve Peers
193+
// - Run `onStart` closure
191194
case(_,.connected):
195+
onStart?()
192196
xpc.connect()
193197
xpc.getPeerState()
194198
tunnelState=.connected

‎Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,23 @@ enum VPNMenuItem: Equatable, Comparable, Identifiable {
4242
}
4343

4444
structMenuItemView:View{
45+
@EnvironmentObjectvarstate:AppState
46+
4547
letitem:VPNMenuItem
4648
letbaseAccessURL:URL
4749
@StateprivatevarnameIsSelected:Bool=false
4850
@StateprivatevarcopyIsSelected:Bool=false
4951

5052
privatevaritemName:AttributedString{
5153
letname=switch item{
52-
caselet.agent(agent): agent.primaryHost??"\(item.wsName).coder"
53-
case.offlineWorkspace:"\(item.wsName).coder"
54+
caselet.agent(agent): agent.primaryHost??"\(item.wsName).\(state.hostnameSuffix)"
55+
case.offlineWorkspace:"\(item.wsName).\(state.hostnameSuffix)"
5456
}
5557

5658
varformattedName=AttributedString(name)
5759
formattedName.foregroundColor=.primary
58-
iflet range= formattedName.range(of:".coder"){
60+
61+
iflet range= formattedName.range(of:".\(state.hostnameSuffix)", options:.backwards){
5962
formattedName[range].foregroundColor=.secondary
6063
}
6164
return formattedName

‎Coder-Desktop/CoderSDK/Util.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import Foundation
2+
3+
publicfunc retry<T>(
4+
floor:Duration,
5+
ceil:Duration,
6+
rate:Double=1.618,
7+
operation:@Sendable()asyncthrows->T
8+
)asyncthrows->T{
9+
vardelay= floor
10+
11+
while !Task.isCancelled{
12+
do{
13+
returntryawaitoperation()
14+
}catchlet error asCancellationError{
15+
throw error
16+
}catch{
17+
tryTask.checkCancellation()
18+
19+
delay=min(ceil, delay* rate)
20+
tryawaitTask.sleep(for: delay)
21+
}
22+
}
23+
24+
throwCancellationError()
25+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import Foundation
2+
3+
publicextensionClient{
4+
func agentConnectionInfoGeneric()asyncthrows(SDKError)->AgentConnectionInfo{
5+
letres=tryawaitrequest("/api/v2/workspaceagents/connection", method:.get)
6+
guard res.resp.statusCode==200else{
7+
throwresponseAsError(res)
8+
}
9+
returntrydecode(AgentConnectionInfo.self, from: res.data)
10+
}
11+
}
12+
13+
publicstructAgentConnectionInfo:Codable,Sendable{
14+
publiclethostname_suffix:String?
15+
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp