11import Foundation
2+ import SwiftUI
23import URLRouting
34
45@MainActor
@@ -20,20 +21,65 @@ class URLHandler {
2021 guard deployment. host ( ) == url. host else {
2122 throw . invalidAuthority( url. host ( ) ?? " <none> " )
2223 }
24+ let route : CoderRoute
2325 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- }
26+ route = try router. match ( url: url)
3127 } catch {
3228 throw . routerError( url: url)
3329 }
3430
35- func handleRDP( workspace _: String , agent _: String , creds _: RDPCredentials ) {
36- // TODO: Handle RDP
31+ switch route {
32+ case let . open( workspace, agent, type) :
33+ switch type {
34+ case let . rdp( creds) :
35+ try handleRDP ( workspace: workspace, agent: agent, creds: creds)
36+ }
37+ }
38+ }
39+
40+ private func handleRDP( workspace: String , agent: String , creds: RDPCredentials ) throws ( URLError) {
41+ guard vpn. state == . connected else {
42+ throw . openError( . coderConnectOffline)
43+ }
44+
45+ guard let workspace = vpn. menuState. findWorkspace ( name: workspace) else {
46+ throw . openError( . invalidWorkspace( workspace: workspace) )
47+ }
48+
49+ guard let agent = vpn. menuState. findAgent ( workspaceID: workspace. id, name: agent) else {
50+ throw . openError( . invalidAgent( workspace: workspace. name, agent: agent) )
51+ }
52+
53+ var rdpString = " rdp:full address=s: \( agent. primaryHost) :3389 "
54+ if let username = creds. username {
55+ rdpString += " &username=s: \( username) "
56+ }
57+ guard let url = URL ( string: rdpString) else {
58+ throw . openError( . couldNotCreateRDPURL( rdpString) )
59+ }
60+
61+ let alert = NSAlert ( )
62+ alert. messageText = " Opening RDP "
63+ alert. informativeText = " Connecting to \( agent. primaryHost) . "
64+ if let username = creds. username {
65+ alert. informativeText += " \n Username: \( username) "
66+ }
67+ if creds. password != nil {
68+ alert. informativeText += " \n The password will be copied to your clipboard. "
69+ }
70+
71+ alert. alertStyle = . informational
72+ alert. addButton ( withTitle: " Open " )
73+ alert. addButton ( withTitle: " Cancel " )
74+ let response = alert. runModal ( )
75+ if response == . alertFirstButtonReturn {
76+ if let password = creds. password {
77+ NSPasteboard . general. clearContents ( )
78+ NSPasteboard . general. setString ( password, forType: . string)
79+ }
80+ NSWorkspace . shared. open ( url)
81+ } else {
82+ // User cancelled
3783 }
3884 }
3985}
@@ -66,6 +112,7 @@ enum URLError: Error {
66112 case invalidAuthority( String )
67113 case routerError( url: URL )
68114 case noSession
115+ case openError( OpenError )
69116
70117 var description : String {
71118 switch self {
@@ -75,6 +122,30 @@ enum URLError: Error {
75122 " Failed to handle \( url. absoluteString) because the format is unsupported. "
76123 case . noSession:
77124 " Not logged in. "
125+ case let . openError( error) :
126+ error. description
127+ }
128+ }
129+
130+ var localizedDescription : String { description }
131+ }
132+
133+ enum OpenError : Error {
134+ case invalidWorkspace( workspace: String )
135+ case invalidAgent( workspace: String , agent: String )
136+ case coderConnectOffline
137+ case couldNotCreateRDPURL( String )
138+
139+ var description : String {
140+ switch self {
141+ case let . invalidWorkspace( ws) :
142+ " Could not find workspace ' \( ws) '. Does it exist? "
143+ case . coderConnectOffline:
144+ " Coder Connect must be running. "
145+ case let . invalidAgent( workspace: workspace, agent: agent) :
146+ " Could not find agent ' \( agent) ' in workspace ' \( workspace) '. Is the workspace running? "
147+ case let . couldNotCreateRDPURL( rdpString) :
148+ " Could not create construct RDP url from ' \( rdpString) '. "
78149 }
79150 }
80151
0 commit comments