mirror of
https://github.com/OpenHands/OpenHands.git
synced 2025-12-26 05:48:36 +08:00
Fix VS Code URL for remote access (#8191)
Co-authored-by: openhands <openhands@all-hands.dev>
This commit is contained in:
parent
e39d904a1f
commit
0fc86b4063
@ -16,6 +16,7 @@ import { BaseModal } from "../../shared/modals/base-modal/base-modal";
|
||||
import { RootState } from "#/store";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { selectSystemMessage } from "#/state/chat-slice";
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
|
||||
interface ConversationCardProps {
|
||||
onClick?: () => void;
|
||||
@ -117,7 +118,10 @@ export function ConversationCard({
|
||||
const data = await response.json();
|
||||
|
||||
if (data.vscode_url) {
|
||||
window.open(data.vscode_url, "_blank");
|
||||
const transformedUrl = transformVSCodeUrl(data.vscode_url);
|
||||
if (transformedUrl) {
|
||||
window.open(transformedUrl, "_blank");
|
||||
}
|
||||
}
|
||||
// VS Code URL not available
|
||||
} catch (error) {
|
||||
|
||||
@ -40,6 +40,7 @@ import { clearFiles, clearInitialPrompt } from "#/state/initial-query-slice";
|
||||
import { RootState } from "#/store";
|
||||
import { displayErrorToast } from "#/utils/custom-toast-handlers";
|
||||
import { useDocumentTitleFromState } from "#/hooks/use-document-title-from-state";
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
|
||||
function AppContent() {
|
||||
useConversationConfig();
|
||||
@ -159,7 +160,12 @@ function AppContent() {
|
||||
);
|
||||
const data = await response.json();
|
||||
if (data.vscode_url) {
|
||||
window.open(data.vscode_url, "_blank");
|
||||
const transformedUrl = transformVSCodeUrl(
|
||||
data.vscode_url,
|
||||
);
|
||||
if (transformedUrl) {
|
||||
window.open(transformedUrl, "_blank");
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
// Silently handle the error
|
||||
|
||||
@ -5,6 +5,7 @@ import { useConversation } from "#/context/conversation-context";
|
||||
import { I18nKey } from "#/i18n/declaration";
|
||||
import { RootState } from "#/store";
|
||||
import { RUNTIME_INACTIVE_STATES } from "#/types/agent-state";
|
||||
import { transformVSCodeUrl } from "#/utils/vscode-url-helper";
|
||||
|
||||
function VSCodeTab() {
|
||||
const { t } = useTranslation();
|
||||
@ -27,7 +28,8 @@ function VSCodeTab() {
|
||||
const data = await response.json();
|
||||
|
||||
if (data.vscode_url) {
|
||||
setVsCodeUrl(data.vscode_url);
|
||||
const transformedUrl = transformVSCodeUrl(data.vscode_url);
|
||||
setVsCodeUrl(transformedUrl);
|
||||
} else {
|
||||
setError(t(I18nKey.VSCODE$URL_NOT_AVAILABLE));
|
||||
}
|
||||
|
||||
61
frontend/src/utils/__tests__/vscode-url-helper.test.ts
Normal file
61
frontend/src/utils/__tests__/vscode-url-helper.test.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
||||
import { transformVSCodeUrl } from "../vscode-url-helper";
|
||||
|
||||
describe("transformVSCodeUrl", () => {
|
||||
const originalWindowLocation = window.location;
|
||||
|
||||
beforeEach(() => {
|
||||
// Mock window.location
|
||||
Object.defineProperty(window, "location", {
|
||||
value: {
|
||||
hostname: "example.com",
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore window.location
|
||||
Object.defineProperty(window, "location", {
|
||||
value: originalWindowLocation,
|
||||
writable: true,
|
||||
});
|
||||
});
|
||||
|
||||
it("should return null if input is null", () => {
|
||||
expect(transformVSCodeUrl(null)).toBeNull();
|
||||
});
|
||||
|
||||
it("should replace localhost with current hostname when they differ", () => {
|
||||
const input = "http://localhost:8080/?tkn=abc123&folder=/workspace";
|
||||
const expected = "http://example.com:8080/?tkn=abc123&folder=/workspace";
|
||||
|
||||
expect(transformVSCodeUrl(input)).toBe(expected);
|
||||
});
|
||||
|
||||
it("should not modify URL if hostname is not localhost", () => {
|
||||
const input = "http://otherhost:8080/?tkn=abc123&folder=/workspace";
|
||||
|
||||
expect(transformVSCodeUrl(input)).toBe(input);
|
||||
});
|
||||
|
||||
it("should not modify URL if current hostname is also localhost", () => {
|
||||
// Change the mocked hostname to localhost
|
||||
Object.defineProperty(window, "location", {
|
||||
value: {
|
||||
hostname: "localhost",
|
||||
},
|
||||
writable: true,
|
||||
});
|
||||
|
||||
const input = "http://localhost:8080/?tkn=abc123&folder=/workspace";
|
||||
|
||||
expect(transformVSCodeUrl(input)).toBe(input);
|
||||
});
|
||||
|
||||
it("should handle invalid URLs gracefully", () => {
|
||||
const input = "not-a-valid-url";
|
||||
|
||||
expect(transformVSCodeUrl(input)).toBe(input);
|
||||
});
|
||||
});
|
||||
31
frontend/src/utils/vscode-url-helper.ts
Normal file
31
frontend/src/utils/vscode-url-helper.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Helper function to transform VS Code URLs
|
||||
*
|
||||
* This function checks if a VS Code URL points to localhost and replaces it with
|
||||
* the current window's hostname if they don't match.
|
||||
*
|
||||
* @param vsCodeUrl The original VS Code URL from the backend
|
||||
* @returns The transformed URL with the correct hostname
|
||||
*/
|
||||
export function transformVSCodeUrl(vsCodeUrl: string | null): string | null {
|
||||
if (!vsCodeUrl) return null;
|
||||
|
||||
try {
|
||||
const url = new URL(vsCodeUrl);
|
||||
|
||||
// Check if the URL points to localhost
|
||||
if (
|
||||
url.hostname === "localhost" &&
|
||||
window.location.hostname !== "localhost"
|
||||
) {
|
||||
// Replace localhost with the current hostname
|
||||
url.hostname = window.location.hostname;
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
return vsCodeUrl;
|
||||
} catch (error) {
|
||||
// Silently handle the error and return the original URL
|
||||
return vsCodeUrl;
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user