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

Commit314edbe

Browse files
feat: support RDP-specific deep links (#147)
Closes#96.If a `coder_app` exists on the workspace, where the URL is of the form: ```coder://dev.coder.com/v0/open/ws/<workspace>/agent/<agent>/rdp?username=administrator&password=password``` the URL will be parsed, validated, and an alert opened. If `Open` is clicked on the alert, the password will be written to the clipboard, where it can be pasted when prompted.https://github.com/user-attachments/assets/da8410c7-d656-4bf7-936a-8d465953e195We're unable to avoid the entering of the password, as the `password` field in an `.rdp` file, even if encrypted properly, is ignored by the macOS Windows RDP app.The app supports reading credentials from the macOS keychain, and whilst we could create keychain entries, they have to be associated with an RDP config in the app, and there's no way to automate the creation of that config, and then run that config.Further reading:https://stackoverflow.com/questions/48713606/how-to-create-rdp-file-on-mac-os-that-allows-auto-loginhttps://techcommunity.microsoft.com/discussions/azurevirtualdesktopforum/macos-remote-desktop-client-app---automatic-logon-no-credential-prompt/2596451The above demo was done by adding this app to the template:```resource "coder_app" "connectrdp" { agent_id = coder_agent.main.id slug = "connectrdp" display_name = "Coder Connect RDP" url = "coder://dev.coder.com/v0/open/ws/${data.coder_workspace.me.name}/agent/main/rdp?username=Administrator&password=coderRDP!" icon = "/icon/terminal.svg" external = true}```
1 parent2198d3e commit314edbe

File tree

6 files changed

+96
-9
lines changed

6 files changed

+96
-9
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
8585
image:"MenuBarIcon",
8686
onAppear:{
8787
// If the VPN is enabled, it's likely the token isn't expired
88-
guardcase.disabled=self.vpn.state,self.state.hasSessionelse{return}
88+
guardself.vpn.state!=.connected,self.state.hasSessionelse{return}
8989
Task{@MainActorin
9090
awaitself.state.handleTokenExpiry()
9191
}

‎Coder-Desktop/Coder-Desktop/URLHandler.swift

Lines changed: 57 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import Foundation
2+
import SwiftUI
23
import VPNLib
34

45
@MainActor
@@ -20,20 +21,69 @@ class URLHandler {
2021
guard deployment.host()== url.hostelse{
2122
throw.invalidAuthority(url.host()??"<none>")
2223
}
24+
letroute:CoderRoute
2325
do{
24-
switchtry router.match(url: url){
25-
caselet.open(workspace, agent, type):
26+
route=try router.match(url: url)
27+
}catch{
28+
throw.matchError(url: url)
29+
}
30+
31+
switch route{
32+
caselet.open(workspace, agent, type):
33+
do{
2634
switch type{
2735
caselet.rdp(creds):
28-
handleRDP(workspace: workspace, agent: agent, creds: creds)
36+
tryhandleRDP(workspace: workspace, agent: agent, creds: creds)
2937
}
38+
}catch{
39+
throw.openError(error)
3040
}
31-
}catch{
32-
throw.matchError(url: url)
41+
}
42+
}
43+
44+
privatefunc handleRDP(workspace:String, agent:String, creds:RDPCredentials)throws(OpenError){
45+
guard vpn.state==.connectedelse{
46+
throw.coderConnectOffline
47+
}
48+
49+
guardlet workspace= vpn.menuState.findWorkspace(name: workspace)else{
50+
throw.invalidWorkspace(workspace: workspace)
51+
}
52+
53+
guardlet agent= vpn.menuState.findAgent(workspaceID: workspace.id, name: agent)else{
54+
throw.invalidAgent(workspace: workspace.name, agent: agent)
55+
}
56+
57+
varrdpString="rdp:full address=s:\(agent.primaryHost):3389"
58+
iflet username= creds.username{
59+
rdpString+="&username=s:\(username)"
60+
}
61+
guardlet url=URL(string: rdpString)else{
62+
throw.couldNotCreateRDPURL(rdpString)
3363
}
3464

35-
func handleRDP(workspace _:String, agent _:String, creds _:RDPCredentials){
36-
// TODO: Handle RDP
65+
letalert=NSAlert()
66+
alert.messageText="Opening RDP"
67+
alert.informativeText="Connecting to\(agent.primaryHost)."
68+
iflet username= creds.username{
69+
alert.informativeText+="\nUsername:\(username)"
70+
}
71+
if creds.password!=nil{
72+
alert.informativeText+="\nThe password will be copied to your clipboard."
73+
}
74+
75+
alert.alertStyle=.informational
76+
alert.addButton(withTitle:"Open")
77+
alert.addButton(withTitle:"Cancel")
78+
letresponse= alert.runModal()
79+
if response==.alertFirstButtonReturn{
80+
iflet password= creds.password{
81+
NSPasteboard.general.clearContents()
82+
NSPasteboard.general.setString(password, forType:.string)
83+
}
84+
NSWorkspace.shared.open(url)
85+
}else{
86+
// User cancelled
3787
}
3888
}
3989
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ struct VPNMenuState {
5858
// or have any invalid UUIDs.
5959
varinvalidAgents:[Vpn_Agent]=[]
6060

61+
publicfunc findAgent(workspaceID:UUID, name:String)->Agent?{
62+
agents.first(where:{ $0.value.wsID== workspaceID && $0.value.name== name})?.value
63+
}
64+
65+
publicfunc findWorkspace(name:String)->Workspace?{
66+
workspaces
67+
.first(where:{ $0.value.name== name})?.value
68+
}
69+
6170
mutatingfunc upsertAgent(_ agent:Vpn_Agent){
6271
guard
6372
let id=UUID(uuidData: agent.id),

‎Coder-Desktop/VPNLib/CoderRouter.swift

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import Foundation
22
import URLRouting
33

44
// This is in VPNLib to avoid depending on `swift-collections` in both the app & extension.
5+
// https://github.com/coder/coder-desktop-macos/issues/149
56
publicstructCoderRouter:ParserPrinter{
67
publicinit(){}
78

@@ -33,6 +34,7 @@ public enum RouterError: Error {
3334
case invalidAuthority(String)
3435
case matchError(url:URL)
3536
case noSession
37+
case openError(OpenError)
3638

3739
publicvardescription:String{
3840
switchself{
@@ -42,6 +44,30 @@ public enum RouterError: Error {
4244
"Failed to handle\(url.absoluteString) because the format is unsupported."
4345
case.noSession:
4446
"Not logged in."
47+
caselet.openError(error):
48+
error.description
49+
}
50+
}
51+
52+
publicvarlocalizedDescription:String{ description}
53+
}
54+
55+
publicenumOpenError:Error{
56+
case invalidWorkspace(workspace:String)
57+
case invalidAgent(workspace:String, agent:String)
58+
case coderConnectOffline
59+
case couldNotCreateRDPURL(String)
60+
61+
publicvardescription:String{
62+
switchself{
63+
caselet.invalidWorkspace(ws):
64+
"Could not find workspace '\(ws)'. Does it exist?"
65+
case.coderConnectOffline:
66+
"Coder Connect must be running."
67+
caselet.invalidAgent(workspace: workspace, agent: agent):
68+
"Could not find agent '\(agent)' in workspace '\(workspace)'. Is the workspace running?"
69+
caselet.couldNotCreateRDPURL(rdpString):
70+
"Could not construct RDP URL from '\(rdpString)'."
4571
}
4672
}
4773

‎Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public protocol FileSyncDaemon: ObservableObject {
2424
func resetSessions(ids:[String])asyncthrows(DaemonError)
2525
}
2626

27+
// File Sync related code is in VPNLib to workaround a linking issue
28+
// https://github.com/coder/coder-desktop-macos/issues/149
2729
@MainActor
2830
publicclassMutagenDaemon:FileSyncDaemon{
2931
letlogger=Logger(subsystem:Bundle.main.bundleIdentifier!, category:"mutagen")

‎scripts/build.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ xcodebuild \
131131
CODE_SIGN_IDENTITY="$CODE_SIGN_IDENTITY" \
132132
CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO \
133133
CODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION=YES \
134-
OTHER_CODE_SIGN_FLAGS='--timestamp'|LC_ALL="en_US.UTF-8" xcpretty
134+
OTHER_CODE_SIGN_FLAGS='--timestamp'|xcbeautify
135135

136136
# Create exportOptions.plist
137137
EXPORT_OPTIONS_PATH="./build/exportOptions.plist"

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp