@@ -17,6 +17,11 @@ import { generateRandomString } from "utils/random";
17
17
import { AgentButton } from "../AgentButton" ;
18
18
import { BaseIcon } from "./BaseIcon" ;
19
19
import { ShareIcon } from "./ShareIcon" ;
20
+ import {
21
+ createAppHref ,
22
+ needsSessionToken ,
23
+ openAppInNewWindow ,
24
+ } from "modules/apps/apps" ;
20
25
21
26
export const DisplayAppNameMap : Record < TypesGen . DisplayApp , string > = {
22
27
port_forwarding_helper : "Ports" ,
@@ -35,57 +40,55 @@ export interface AppLinkProps {
35
40
workspace : TypesGen . Workspace ;
36
41
app : TypesGen . WorkspaceApp ;
37
42
agent : TypesGen . WorkspaceAgent ;
43
+ token ?: string ;
38
44
}
39
45
40
- export const AppLink : FC < AppLinkProps > = ( { app, workspace, agent } ) => {
46
+ export const AppLink : FC < AppLinkProps > = ( { app, workspace, agent, token } ) => {
41
47
const { proxy } = useProxy ( ) ;
42
- const preferredPathBase = proxy . preferredPathAppURL ;
43
- const appsHost = proxy . preferredWildcardHostname ;
44
- const [ fetchingSessionToken , setFetchingSessionToken ] = useState ( false ) ;
48
+ const host = proxy . preferredWildcardHostname ;
45
49
const [ iconError , setIconError ] = useState ( false ) ;
46
50
const theme = useTheme ( ) ;
47
- const username = workspace . owner_name ;
48
- const displayName = app . display_name || app . slug ;
49
-
50
- const href = createAppLinkHref (
51
- window . location . protocol ,
52
- preferredPathBase ,
53
- appsHost ,
54
- app . slug ,
55
- username ,
56
- workspace ,
51
+ const displayName = app . display_name ?? app . slug ;
52
+ const href = createAppHref ( app , {
57
53
agent,
58
- app ,
59
- ) ;
54
+ workspace,
55
+ token,
56
+ path : proxy . preferredPathAppURL ,
57
+ host : proxy . preferredWildcardHostname ,
58
+ } ) ;
60
59
61
60
// canClick is ONLY false when it's a subdomain app and the admin hasn't
62
61
// enabled wildcard access URL or the session token is being fetched.
63
62
//
64
63
// To avoid bugs in the healthcheck code locking users out of apps, we no
65
64
// longer block access to apps if they are unhealthy/initializing.
66
65
let canClick = true ;
66
+ let primaryTooltip = "" ;
67
67
let icon = ! iconError && (
68
68
< BaseIcon app = { app } onIconPathError = { ( ) => setIconError ( true ) } />
69
69
) ;
70
70
71
- let primaryTooltip = "" ;
72
71
if ( app . health === "initializing" ) {
73
72
icon = < Spinner loading /> ;
74
73
primaryTooltip = "Initializing..." ;
75
74
}
75
+
76
76
if ( app . health === "unhealthy" ) {
77
77
icon = < ErrorOutlineIcon css = { { color : theme . palette . warning . light } } /> ;
78
78
primaryTooltip = "Unhealthy" ;
79
79
}
80
- if ( ! appsHost && app . subdomain ) {
80
+
81
+ if ( ! host && app . subdomain ) {
81
82
canClick = false ;
82
83
icon = < ErrorOutlineIcon css = { { color : theme . palette . grey [ 300 ] } } /> ;
83
84
primaryTooltip =
84
85
"Your admin has not configured subdomain application access" ;
85
86
}
86
- if ( fetchingSessionToken ) {
87
+
88
+ if ( ! token && needsSessionToken ( app ) ) {
87
89
canClick = false ;
88
90
}
91
+
89
92
if (
90
93
agent . lifecycle_state === "starting" &&
91
94
agent . startup_script_behavior === "blocking"
@@ -99,32 +102,12 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
99
102
< AgentButton asChild >
100
103
< a
101
104
href = { canClick ? href : undefined }
102
- onClick = { async ( event ) => {
105
+ onClick = { ( event ) => {
103
106
if ( ! canClick ) {
104
107
return ;
105
108
}
106
109
107
- event . preventDefault ( ) ;
108
-
109
- // HTTP links should never need the session token, since Cookies
110
- // handle sharing it when you access the Coder Dashboard. We should
111
- // never be forwarding the bare session token to other domains!
112
- const isHttp = app . url ?. startsWith ( "http" ) ;
113
- if ( app . external && ! isHttp ) {
114
- // This is a magic undocumented string that is replaced
115
- // with a brand-new session token from the backend.
116
- // This only exists for external URLs, and should only
117
- // be used internally, and is highly subject to break.
118
- const magicTokenString = "$SESSION_TOKEN" ;
119
- const hasMagicToken = href . indexOf ( magicTokenString ) ;
120
- let url = href ;
121
- if ( hasMagicToken !== - 1 ) {
122
- setFetchingSessionToken ( true ) ;
123
- const key = await API . getApiKey ( ) ;
124
- url = href . replaceAll ( magicTokenString , key . key ) ;
125
- setFetchingSessionToken ( false ) ;
126
- }
127
-
110
+ if ( app . external ) {
128
111
// When browser recognizes the protocol and is able to navigate to the app,
129
112
// it will blur away, and will stop the timer. Otherwise,
130
113
// an error message will be displayed.
@@ -135,22 +118,12 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
135
118
window . addEventListener ( "blur" , ( ) => {
136
119
clearTimeout ( openAppExternallyFailed ) ;
137
120
} ) ;
138
-
139
- window . location . href = url ;
140
- return ;
141
121
}
142
122
143
123
switch ( app . open_in ) {
144
124
case "slim-window" : {
145
- window . open (
146
- href ,
147
- Language . appTitle ( displayName , generateRandomString ( 12 ) ) ,
148
- "width=900,height=600" ,
149
- ) ;
150
- return ;
151
- }
152
- default : {
153
- window . open ( href ) ;
125
+ event . preventDefault ( ) ;
126
+ openAppInNewWindow ( href ) ;
154
127
return ;
155
128
}
156
129
}
0 commit comments