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

Lines changed: 54 additions & 9 deletions
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

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
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

Lines changed: 26 additions & 1 deletion
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy