Skip to content

Commit b093806

Browse files
committed
chore: extract app access logic
1 parent 830ab57 commit b093806

File tree

10 files changed

+254
-234
lines changed

10 files changed

+254
-234
lines changed

site/src/modules/apps/apps.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export const openAppInNewWindow = (href: string) => {
6565
window.open(href, "_blank", "width=900,height=600");
6666
};
6767

68-
type CreateAppHrefParams = {
68+
export type GetAppHrefParams = {
6969
path: string;
7070
host: string;
7171
workspace: Workspace;
@@ -75,7 +75,7 @@ type CreateAppHrefParams = {
7575

7676
export const getAppHref = (
7777
app: WorkspaceApp,
78-
{ path, token, workspace, agent, host }: CreateAppHrefParams,
78+
{ path, token, workspace, agent, host }: GetAppHrefParams,
7979
): string => {
8080
if (isExternalApp(app)) {
8181
return needsSessionToken(app)

site/src/modules/apps/useAppLink.ts

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { apiKey } from "api/queries/users";
2+
import type {
3+
Workspace,
4+
WorkspaceAgent,
5+
WorkspaceApp,
6+
} from "api/typesGenerated";
7+
import { displayError } from "components/GlobalSnackbar/utils";
8+
import { useProxy } from "contexts/ProxyContext";
9+
import type React from "react";
10+
import { useQuery } from "react-query";
11+
import {
12+
getAppHref,
13+
isExternalApp,
14+
needsSessionToken,
15+
openAppInNewWindow,
16+
} from "./apps";
17+
18+
type UseAppLinkParams = {
19+
workspace: Workspace;
20+
agent: WorkspaceAgent;
21+
};
22+
23+
export const useAppLink = (
24+
app: WorkspaceApp,
25+
{ agent, workspace }: UseAppLinkParams,
26+
) => {
27+
const label = app.display_name ?? app.slug;
28+
const { proxy } = useProxy();
29+
const { data: apiKeyResponse } = useQuery({
30+
...apiKey(),
31+
enabled: isExternalApp(app) && needsSessionToken(app),
32+
});
33+
34+
const href = getAppHref(app, {
35+
agent,
36+
workspace,
37+
token: apiKeyResponse?.key ?? "",
38+
path: proxy.preferredPathAppURL,
39+
host: proxy.preferredWildcardHostname,
40+
});
41+
42+
const onClick = (e: React.MouseEvent) => {
43+
if (!e.currentTarget.getAttribute("href")) {
44+
return;
45+
}
46+
47+
if (app.external) {
48+
// When browser recognizes the protocol and is able to navigate to the app,
49+
// it will blur away, and will stop the timer. Otherwise,
50+
// an error message will be displayed.
51+
const openAppExternallyFailedTimeout = 500;
52+
const openAppExternallyFailed = setTimeout(() => {
53+
displayError(`${label} must be installed first.`);
54+
}, openAppExternallyFailedTimeout);
55+
window.addEventListener("blur", () => {
56+
clearTimeout(openAppExternallyFailed);
57+
});
58+
}
59+
60+
switch (app.open_in) {
61+
case "slim-window": {
62+
e.preventDefault();
63+
openAppInNewWindow(href);
64+
return;
65+
}
66+
}
67+
};
68+
69+
return {
70+
href,
71+
onClick,
72+
label,
73+
hasToken: !!apiKeyResponse?.key,
74+
};
75+
};

site/src/modules/resources/AgentRow.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ export interface AgentRowProps {
5454
onUpdateAgent: () => void;
5555
template: Template;
5656
storybookAgentMetadata?: WorkspaceAgentMetadata[];
57-
token?: string;
5857
}
5958

6059
export const AgentRow: FC<AgentRowProps> = ({
@@ -70,7 +69,6 @@ export const AgentRow: FC<AgentRowProps> = ({
7069
onUpdateAgent,
7170
storybookAgentMetadata,
7271
sshPrefix,
73-
token,
7472
}) => {
7573
// Apps visibility
7674
const visibleApps = agent.apps.filter((app) => !app.hidden);
@@ -241,7 +239,6 @@ export const AgentRow: FC<AgentRowProps> = ({
241239
)}
242240
{visibleApps.map((app) => (
243241
<AppLink
244-
token={token}
245242
key={app.slug}
246243
app={app}
247244
agent={agent}

site/src/modules/resources/AppLink/AppLink.tsx

Lines changed: 7 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { useTheme } from "@emotion/react";
22
import ErrorOutlineIcon from "@mui/icons-material/ErrorOutline";
33
import type * as TypesGen from "api/typesGenerated";
4-
import { displayError } from "components/GlobalSnackbar/utils";
54
import { Spinner } from "components/Spinner/Spinner";
65
import {
76
Tooltip,
@@ -10,11 +9,8 @@ import {
109
TooltipTrigger,
1110
} from "components/Tooltip/Tooltip";
1211
import { useProxy } from "contexts/ProxyContext";
13-
import {
14-
getAppHref,
15-
needsSessionToken,
16-
openAppInNewWindow,
17-
} from "modules/apps/apps";
12+
import { needsSessionToken } from "modules/apps/apps";
13+
import { useAppLink } from "modules/apps/useAppLink";
1814
import { type FC, useState } from "react";
1915
import { AgentButton } from "../AgentButton";
2016
import { BaseIcon } from "./BaseIcon";
@@ -28,31 +24,18 @@ export const DisplayAppNameMap: Record<TypesGen.DisplayApp, string> = {
2824
web_terminal: "Terminal",
2925
};
3026

31-
const Language = {
32-
appTitle: (appName: string, identifier: string): string =>
33-
`${appName} - ${identifier}`,
34-
};
35-
3627
export interface AppLinkProps {
3728
workspace: TypesGen.Workspace;
3829
app: TypesGen.WorkspaceApp;
3930
agent: TypesGen.WorkspaceAgent;
40-
token?: string;
4131
}
4232

43-
export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent, token }) => {
33+
export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent }) => {
4434
const { proxy } = useProxy();
4535
const host = proxy.preferredWildcardHostname;
4636
const [iconError, setIconError] = useState(false);
4737
const theme = useTheme();
48-
const displayName = app.display_name ?? app.slug;
49-
const href = getAppHref(app, {
50-
agent,
51-
workspace,
52-
token,
53-
path: proxy.preferredPathAppURL,
54-
host: proxy.preferredWildcardHostname,
55-
});
38+
const link = useAppLink(app, { agent, workspace });
5639

5740
// canClick is ONLY false when it's a subdomain app and the admin hasn't
5841
// enabled wildcard access URL or the session token is being fetched.
@@ -82,7 +65,7 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent, token }) => {
8265
"Your admin has not configured subdomain application access";
8366
}
8467

85-
if (!token && needsSessionToken(app)) {
68+
if (needsSessionToken(app) && !link.hasToken) {
8669
canClick = false;
8770
}
8871

@@ -97,37 +80,9 @@ export const AppLink: FC<AppLinkProps> = ({ app, workspace, agent, token }) => {
9780

9881
const button = (
9982
<AgentButton asChild>
100-
<a
101-
href={canClick ? href : undefined}
102-
onClick={(event) => {
103-
if (!canClick) {
104-
return;
105-
}
106-
107-
if (app.external) {
108-
// When browser recognizes the protocol and is able to navigate to the app,
109-
// it will blur away, and will stop the timer. Otherwise,
110-
// an error message will be displayed.
111-
const openAppExternallyFailedTimeout = 500;
112-
const openAppExternallyFailed = setTimeout(() => {
113-
displayError(`${displayName} must be installed first.`);
114-
}, openAppExternallyFailedTimeout);
115-
window.addEventListener("blur", () => {
116-
clearTimeout(openAppExternallyFailed);
117-
});
118-
}
119-
120-
switch (app.open_in) {
121-
case "slim-window": {
122-
event.preventDefault();
123-
openAppInNewWindow(href);
124-
return;
125-
}
126-
}
127-
}}
128-
>
83+
<a href={canClick ? link.href : undefined} onClick={link.onClick}>
12984
{icon}
130-
{displayName}
85+
{link.label}
13186
{canShare && <ShareIcon app={app} />}
13287
</a>
13388
</AgentButton>

0 commit comments

Comments
 (0)