@@ -4,14 +4,15 @@ import { makeStyles } from "@mui/styles";
4
4
import Tooltip from "@mui/material/Tooltip" ;
5
5
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline" ;
6
6
import { PrimaryAgentButton } from "components/Resources/AgentButton" ;
7
- import { FC } from "react" ;
7
+ import { FC , useState } from "react" ;
8
8
import { combineClasses } from "utils/combineClasses" ;
9
9
import * as TypesGen from "../../../api/typesGenerated" ;
10
10
import { generateRandomString } from "../../../utils/random" ;
11
11
import { BaseIcon } from "./BaseIcon" ;
12
12
import { ShareIcon } from "./ShareIcon" ;
13
13
import { useProxy } from "contexts/ProxyContext" ;
14
14
import { createAppLinkHref } from "utils/apps" ;
15
+ import { getApiKey } from "api/api" ;
15
16
16
17
const Language = {
17
18
appTitle : ( appName : string , identifier : string ) : string =>
@@ -28,6 +29,7 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
28
29
const { proxy } = useProxy ( ) ;
29
30
const preferredPathBase = proxy . preferredPathAppURL ;
30
31
const appsHost = proxy . preferredWildcardHostname ;
32
+ const [ fetchingSessionToken , setFetchingSessionToken ] = useState ( false ) ;
31
33
32
34
const styles = useStyles ( ) ;
33
35
const username = workspace . owner_name ;
@@ -72,6 +74,9 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
72
74
primaryTooltip =
73
75
"Your admin has not configured subdomain application access" ;
74
76
}
77
+ if ( fetchingSessionToken ) {
78
+ canClick = false ;
79
+ }
75
80
76
81
const isPrivateApp = app . sharing_level === "owner" ;
77
82
@@ -96,13 +101,38 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
96
101
className = { canClick ? styles . link : styles . disabledLink }
97
102
onClick = {
98
103
canClick
99
- ? ( event ) => {
104
+ ? async ( event ) => {
100
105
event . preventDefault ( ) ;
101
- window . open (
102
- href ,
103
- Language . appTitle ( appDisplayName , generateRandomString ( 12 ) ) ,
104
- "width=900,height=600" ,
105
- ) ;
106
+ // This is an external URI like "vscode://", so
107
+ // it needs to be opened with the browser protocol handler.
108
+ if ( app . external && ! app . url . startsWith ( "http" ) ) {
109
+ // If the protocol is external the browser does not
110
+ // redirect the user from the page.
111
+
112
+ // This is a magic undocumented string that is replaced
113
+ // with a brand-new session token from the backend.
114
+ // This only exists for external URLs, and should only
115
+ // be used internally, and is highly subject to break.
116
+ const magicTokenString = "$SESSION_TOKEN" ;
117
+ const hasMagicToken = href . indexOf ( magicTokenString ) ;
118
+ let url = href ;
119
+ if ( hasMagicToken !== - 1 ) {
120
+ setFetchingSessionToken ( true ) ;
121
+ const key = await getApiKey ( ) ;
122
+ url = href . replaceAll ( magicTokenString , key . key ) ;
123
+ setFetchingSessionToken ( false ) ;
124
+ }
125
+ window . location . href = url ;
126
+ } else {
127
+ window . open (
128
+ href ,
129
+ Language . appTitle (
130
+ appDisplayName ,
131
+ generateRandomString ( 12 ) ,
132
+ ) ,
133
+ "width=900,height=600" ,
134
+ ) ;
135
+ }
106
136
}
107
137
: undefined
108
138
}
0 commit comments