Skip to content

Commit 7a96c5a

Browse files
committed
feat: support RDP-specific deep links
1 parent e39714c commit 7a96c5a

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
@@ -33,15 +33,40 @@ public enum RouterError: Error {
3333
case invalidAuthority(String)
3434
case matchError(url: URL)
3535
case noSession
36+
case openError(OpenError)
3637

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

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