mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site): eagerly fetch API key for Open in Cursor/VS Code buttons (#22554)
## Problem The **Open in Cursor** and **Open in VS Code** buttons on the agent detail page were broken. Clicking them did nothing. ### Root Cause The `handleOpenInEditor` handler in `AgentDetail.tsx` called `window.location.assign()` with a custom protocol URI (`vscode://` or `cursor://`) **after** an `await API.getApiKey()` call. This creates an async boundary that breaks the browser's user gesture chain, causing custom protocol navigations (`vscode://`, `cursor://`) to be silently blocked by the browser. The handler was invoked from a Radix `DropdownMenuItem.onSelect`, which adds another layer of event indirection that makes the gesture chain more fragile. In contrast, the workspace page's `VSCodeDesktopButton` works because it uses a direct `onClick` handler on a button element. ## Fix - **Eagerly fetch and cache the API key** via `useQuery` when workspace and agent data is available - **Make `handleOpenInEditor` synchronous** — it reads the cached key instead of awaiting a network call, keeping `window.location.assign()` within the original user gesture context - **Disable buttons** while the API key is still loading (`canOpenEditors` now gates on key availability) - **Simplify** the `onOpenInEditor` callback (remove `void` async wrapper)
This commit is contained in:
@@ -37,7 +37,7 @@ type GetVSCodeHrefParams = {
|
||||
};
|
||||
|
||||
export const getVSCodeHref = (
|
||||
app: "vscode" | "vscode-insiders",
|
||||
app: "vscode" | "vscode-insiders" | "cursor",
|
||||
{ owner, workspace, token, agent, folder }: GetVSCodeHrefParams,
|
||||
) => {
|
||||
const query = new URLSearchParams({
|
||||
|
||||
@@ -21,7 +21,6 @@ import {
|
||||
getTerminalHref,
|
||||
getVSCodeHref,
|
||||
openAppInNewWindow,
|
||||
SESSION_TOKEN_PLACEHOLDER,
|
||||
} from "modules/apps/apps";
|
||||
import {
|
||||
type FC,
|
||||
@@ -839,45 +838,33 @@ const AgentDetail: FC = () => {
|
||||
: undefined;
|
||||
const shouldShowDiffPanel = hasDiffStatus && showDiffPanel;
|
||||
|
||||
const handleOpenInEditor = async (editor: "cursor" | "vscode") => {
|
||||
const generateKeyMutation = useMutation({
|
||||
mutationFn: () => API.getApiKey(),
|
||||
});
|
||||
|
||||
const handleOpenInEditor = (editor: "cursor" | "vscode") => {
|
||||
if (!workspace || !workspaceAgent) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { key } = await API.getApiKey();
|
||||
const vscodeHref = getVSCodeHref("vscode", {
|
||||
owner: workspace.owner_name,
|
||||
workspace: workspace.name,
|
||||
token: key,
|
||||
agent: workspaceAgent.name,
|
||||
folder: workspaceAgent.expanded_directory,
|
||||
});
|
||||
|
||||
if (editor === "cursor") {
|
||||
const cursorApp = workspaceAgent.apps.find((app) => {
|
||||
const name = (app.display_name ?? app.slug).toLowerCase();
|
||||
return app.slug.toLowerCase() === "cursor" || name === "cursor";
|
||||
generateKeyMutation.mutate(undefined, {
|
||||
onSuccess: ({ key }) => {
|
||||
location.href = getVSCodeHref(editor, {
|
||||
owner: workspace.owner_name,
|
||||
workspace: workspace.name,
|
||||
token: key,
|
||||
agent: workspaceAgent.name,
|
||||
folder: workspaceAgent.expanded_directory,
|
||||
});
|
||||
if (cursorApp?.external && cursorApp.url) {
|
||||
const href = cursorApp.url.includes(SESSION_TOKEN_PLACEHOLDER)
|
||||
? cursorApp.url.replaceAll(SESSION_TOKEN_PLACEHOLDER, key)
|
||||
: cursorApp.url;
|
||||
window.location.assign(href);
|
||||
return;
|
||||
}
|
||||
window.location.assign(vscodeHref.replace(/^vscode:/, "cursor:"));
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.assign(vscodeHref);
|
||||
} catch {
|
||||
toast.error(
|
||||
editor === "cursor"
|
||||
? "Failed to open in Cursor."
|
||||
: "Failed to open in VS Code.",
|
||||
);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast.error(
|
||||
editor === "cursor"
|
||||
? "Failed to open in Cursor."
|
||||
: "Failed to open in VS Code.",
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleViewWorkspace = () => {
|
||||
@@ -1035,9 +1022,7 @@ const AgentDetail: FC = () => {
|
||||
workspace={{
|
||||
canOpenEditors,
|
||||
canOpenWorkspace,
|
||||
onOpenInEditor: (editor) => {
|
||||
void handleOpenInEditor(editor);
|
||||
},
|
||||
onOpenInEditor: handleOpenInEditor,
|
||||
onViewWorkspace: handleViewWorkspace,
|
||||
onOpenTerminal: handleOpenTerminal,
|
||||
sshCommand,
|
||||
|
||||
Reference in New Issue
Block a user