1111
1212namespace Coder . Desktop . App . Services ;
1313
14- public interface IUriHandler : IAsyncDisposable
14+ public interface IUriHandler
1515{
1616 public Task HandleUri ( Uri uri , CancellationToken ct = default ) ;
1717}
@@ -24,10 +24,16 @@ public class UriHandler(
2424{
2525 private const string OpenWorkspacePrefix = "/v0/open/ws/" ;
2626
27- internal class UriException ( string title , string detail ) : Exception
27+ internal class UriException : Exception
2828 {
29- internal readonly string Title = title ;
30- internal readonly string Detail = detail ;
29+ internal readonly string Title ;
30+ internal readonly string Detail ;
31+
32+ internal UriException ( string title , string detail ) : base ( $ "{ title } : { detail } ")
33+ {
34+ Title = title ;
35+ Detail = detail ;
36+ }
3137 }
3238
3339 public async Task HandleUri ( Uri uri , CancellationToken ct = default )
@@ -52,7 +58,7 @@ private async Task HandleUriThrowingErrors(Uri uri, CancellationToken ct = defau
5258
5359 logger . LogWarning ( "unhandled URI path {path}" , uri . AbsolutePath ) ;
5460 throw new UriException ( "URI handling error" ,
55- $ "URI with path { uri . AbsolutePath } is unsupported or malformed") ;
61+ $ "URI with path ' { uri . AbsolutePath } ' is unsupported or malformed") ;
5662 }
5763
5864 public async Task HandleOpenWorkspaceApp ( Uri uri , CancellationToken ct = default )
@@ -63,7 +69,7 @@ public async Task HandleOpenWorkspaceApp(Uri uri, CancellationToken ct = default
6369 if ( components . Length != 4 || components [ 1 ] != "agent" )
6470 {
6571 logger . LogWarning ( "unsupported open workspace app format in URI {path}" , uri . AbsolutePath ) ;
66- throw new UriException ( errTitle , $ "Failed to open { uri . AbsolutePath } because the format is unsupported.") ;
72+ throw new UriException ( errTitle , $ "Failed to open ' { uri . AbsolutePath } ' because the format is unsupported.") ;
6773 }
6874
6975 var workspaceName = components [ 0 ] ;
@@ -73,41 +79,38 @@ public async Task HandleOpenWorkspaceApp(Uri uri, CancellationToken ct = default
7379 var state = rpcController . GetState ( ) ;
7480 if ( state . VpnLifecycle != VpnLifecycle . Started )
7581 {
76- logger . LogDebug ( "got URI to open workspace {workspace}, but Coder Connect is not started" , workspaceName ) ;
82+ logger . LogDebug ( "got URI to open workspace ' {workspace}' , but Coder Connect is not started" , workspaceName ) ;
7783 throw new UriException ( errTitle ,
78- $ "Failed to open application on { workspaceName } because Coder Connect is not started.") ;
84+ $ "Failed to open application on ' { workspaceName } ' because Coder Connect is not started.") ;
7985 }
8086
81- Workspace workspace ;
82- try
83- {
84- workspace = state . Workspaces . Single ( w => w . Name == workspaceName ) ;
85- }
86- catch ( InvalidOperationException ) // Single() throws this when nothing matches.
87- {
88- logger . LogDebug ( "got URI to open workspace {workspace}, but the workspace doesn't exist" , workspaceName ) ;
87+ var workspace = state . Workspaces . FirstOrDefault ( w => w . Name == workspaceName ) ;
88+ if ( workspace == null ) {
89+ logger . LogDebug ( "got URI to open workspace '{workspace}', but the workspace doesn't exist" , workspaceName ) ;
8990 throw new UriException ( errTitle ,
90- $ "Failed to open application on workspace { workspaceName } because it doesn't exist") ;
91+ $ "Failed to open application on workspace ' { workspaceName } ' because it doesn't exist") ;
9192 }
9293
93- Agent agent ;
94- try
95- {
96- agent = state . Agents . Single ( a => a . WorkspaceId == workspace . Id && a . Name == agentName ) ;
97- }
98- catch ( InvalidOperationException ) // Single() throws this when nothing matches.
99- {
100- logger . LogDebug ( "got URI to open workspace/agent {workspaceName}/{agentName}, but the agent doesn't exist" ,
94+ var agent = state . Agents . FirstOrDefault ( a => a . WorkspaceId == workspace . Id && a . Name == agentName ) ;
95+ if ( agent == null ) {
96+ logger . LogDebug ( "got URI to open workspace/agent '{workspaceName}/{agentName}', but the agent doesn't exist" ,
10197 workspaceName , agentName ) ;
98+ // If the workspace isn't running, that is almost certainly why we can't find the agent, so report that
99+ // to the user.
100+ if ( workspace . Status != Workspace . Types . Status . Running )
101+ {
102+ throw new UriException ( errTitle ,
103+ $ "Failed to open application on workspace '{ workspaceName } ', because the workspace is not running.") ;
104+ }
102105 throw new UriException ( errTitle ,
103- $ "Failed to open application on workspace { workspaceName } , agent { agentName } because it doesn't exist.") ;
106+ $ "Failed to open application on workspace ' { workspaceName } ', because agent ' { agentName } ' doesn't exist.") ;
104107 }
105108
106109 if ( appName != "rdp" )
107110 {
108111 logger . LogWarning ( "unsupported agent application type {app}" , appName ) ;
109112 throw new UriException ( errTitle ,
110- $ "Failed to open agent in URI { uri . AbsolutePath } because application { appName } is unsupported") ;
113+ $ "Failed to open agent in URI ' { uri . AbsolutePath } ' because application ' { appName } ' is unsupported") ;
111114 }
112115
113116 await OpenRDP ( agent . Fqdn . First ( ) , uri . Query , ct ) ;
@@ -137,14 +140,9 @@ public async Task OpenRDP(string domainName, string queryString, CancellationTok
137140 if ( ! string . IsNullOrEmpty ( username ) )
138141 {
139142 password ??= string . Empty ;
140- await rdpConnector . WriteCredentials ( domainName , new RdpCredentials ( username , password ) , ct ) ;
143+ rdpConnector . WriteCredentials ( domainName , new RdpCredentials ( username , password ) ) ;
141144 }
142145
143146 await rdpConnector . Connect ( domainName , ct : ct ) ;
144147 }
145-
146- public ValueTask DisposeAsync ( )
147- {
148- return ValueTask . CompletedTask ;
149- }
150148}
0 commit comments