Skip to content

Commit 6f251f5

Browse files
committed
feat: support RDP-specific deep links
1 parent 83b1554 commit 6f251f5

File tree

3 files changed

+89
-10
lines changed

3 files changed

+89
-10
lines changed

Coder-Desktop/Coder-Desktop/URLHandler.swift

+54-9
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,65 @@ class URLHandler {
2020
guard deployment.host() == url.host else {
2121
throw .invalidAuthority(url.host() ?? "<none>")
2222
}
23+
let route: CoderRoute
2324
do {
24-
switch try router.match(url: url) {
25-
case let .open(workspace, agent, type):
26-
switch type {
27-
case let .rdp(creds):
28-
handleRDP(workspace: workspace, agent: agent, creds: creds)
29-
}
30-
}
25+
route = try router.match(url: url)
3126
} catch {
3227
throw .matchError(url: url)
3328
}
3429

35-
func handleRDP(workspace _: String, agent _: String, creds _: RDPCredentials) {
36-
// TODO: Handle RDP
30+
switch route {
31+
case let .open(workspace, agent, type):
32+
switch type {
33+
case let .rdp(creds):
34+
try handleRDP(workspace: workspace, agent: agent, creds: creds)
35+
}
36+
}
37+
}
38+
39+
private func handleRDP(workspace: String, agent: String, creds: RDPCredentials) throws(URLError) {
40+
guard vpn.state == .connected else {
41+
throw .openError(.coderConnectOffline)
42+
}
43+
44+
guard let workspace = vpn.menuState.findWorkspace(name: workspace) else {
45+
throw .openError(.invalidWorkspace(workspace: workspace))
46+
}
47+
48+
guard let agent = vpn.menuState.findAgent(workspaceID: workspace.id, name: agent) else {
49+
throw .openError(.invalidAgent(workspace: workspace.name, agent: agent))
50+
}
51+
52+
var rdpString = "rdp:full address=s:\(agent.primaryHost):3389"
53+
if let username = creds.username {
54+
rdpString += "&username=s:\(username)"
55+
}
56+
guard let url = URL(string: rdpString) else {
57+
throw .openError(.couldNotCreateRDPURL(rdpString))
58+
}
59+
60+
let alert = NSAlert()
61+
alert.messageText = "Opening RDP"
62+
alert.informativeText = "Connecting to \(agent.primaryHost)."
63+
if let username = creds.username {
64+
alert.informativeText += "\nUsername: \(username)"
65+
}
66+
if creds.password != nil {
67+
alert.informativeText += "\nThe password will be copied to your clipboard."
68+
}
69+
70+
alert.alertStyle = .informational
71+
alert.addButton(withTitle: "Open")
72+
alert.addButton(withTitle: "Cancel")
73+
let response = alert.runModal()
74+
if response == .alertFirstButtonReturn {
75+
if let password = creds.password {
76+
NSPasteboard.general.clearContents()
77+
NSPasteboard.general.setString(password, forType: .string)
78+
}
79+
NSWorkspace.shared.open(url)
80+
} else {
81+
// User cancelled
3782
}
3883
}
3984
}

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

+9
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ struct VPNMenuState {
5858
// or have any invalid UUIDs.
5959
var invalidAgents: [Vpn_Agent] = []
6060

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

Coder-Desktop/VPNLib/CoderRouter.swift

+26-1
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,40 @@ public enum RouterError: Error {
3232
case invalidAuthority(String)
3333
case matchError(url: URL)
3434
case noSession
35+
case openError(OpenError)
3536

36-
public var description: String {
37+
var description: String {
3738
switch self {
3839
case let .invalidAuthority(authority):
3940
"Authority '\(authority)' does not match the host of the current Coder deployment."
4041
case let .matchError(url):
4142
"Failed to handle \(url.absoluteString) because the format is unsupported."
4243
case .noSession:
4344
"Not logged in."
45+
case let .openError(error):
46+
error.description
47+
}
48+
}
49+
50+
var localizedDescription: String { description }
51+
}
52+
53+
public enum OpenError: Error {
54+
case invalidWorkspace(workspace: String)
55+
case invalidAgent(workspace: String, agent: String)
56+
case coderConnectOffline
57+
case couldNotCreateRDPURL(String)
58+
59+
public var description: String {
60+
switch self {
61+
case let .invalidWorkspace(ws):
62+
"Could not find workspace '\(ws)'. Does it exist?"
63+
case .coderConnectOffline:
64+
"Coder Connect must be running."
65+
case let .invalidAgent(workspace: workspace, agent: agent):
66+
"Could not find agent '\(agent)' in workspace '\(workspace)'. Is the workspace running?"
67+
case let .couldNotCreateRDPURL(rdpString):
68+
"Could not create construct RDP url from '\(rdpString)'."
4469
}
4570
}
4671

0 commit comments

Comments
 (0)