Compare commits

...

6 Commits

Author SHA1 Message Date
Lluis Agusti
c6c539e48e chore: cleanup 2025-07-03 18:06:44 +04:00
Lluis Agusti
8bbd2a8125 chore: refactors 2025-07-03 18:02:00 +04:00
Lluis Agusti
5b4ce4c93f chore: fixes 2025-07-03 17:41:17 +04:00
Lluis Agusti
1210e8ef04 Merge 'dev' into 'fix/proxy-via-api-no-actions' 2025-07-03 17:33:31 +04:00
Lluis Agusti
dc84b777c0 chore: changes 2025-07-03 17:33:20 +04:00
Lluis Agusti
bed812d7a5 chore: fix 2025-07-03 13:40:52 +04:00
3 changed files with 154 additions and 32 deletions

View File

@@ -1,23 +1,5 @@
import { FC, useEffect, useMemo, useState } from "react";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import SchemaTooltip from "@/components/SchemaTooltip";
import useCredentials from "@/hooks/useCredentials";
import { NotionLogoIcon } from "@radix-ui/react-icons";
import {
FaDiscord,
FaGithub,
FaTwitter,
FaGoogle,
FaMedium,
FaKey,
FaHubspot,
} from "react-icons/fa";
import {
BlockIOCredentialsSubSchema,
CredentialsMetaInput,
CredentialsProviderName,
} from "@/lib/autogpt-server-api/types";
import { Button } from "@/components/ui/button";
import { IconKey, IconKeyPlus, IconUserPlus } from "@/components/ui/icons";
import {
Select,
@@ -27,12 +9,30 @@ import {
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import useCredentials from "@/hooks/useCredentials";
import { useBackendAPI } from "@/lib/autogpt-server-api/context";
import {
BlockIOCredentialsSubSchema,
CredentialsMetaInput,
CredentialsProviderName,
} from "@/lib/autogpt-server-api/types";
import { cn } from "@/lib/utils";
import { getHostFromUrl } from "@/lib/utils/url";
import { NotionLogoIcon } from "@radix-ui/react-icons";
import { FC, useEffect, useMemo, useState } from "react";
import {
FaDiscord,
FaGithub,
FaGoogle,
FaHubspot,
FaKey,
FaMedium,
FaTwitter,
} from "react-icons/fa";
import { APIKeyCredentialsModal } from "./api-key-credentials-modal";
import { UserPasswordCredentialsModal } from "./user-password-credentials-modal";
import { HostScopedCredentialsModal } from "./host-scoped-credentials-modal";
import { OAuth2FlowWaitingModal } from "./oauth2-flow-waiting-modal";
import { getHostFromUrl } from "@/lib/utils/url";
import { UserPasswordCredentialsModal } from "./user-password-credentials-modal";
const fallbackIcon = FaKey;

View File

@@ -2,7 +2,6 @@ import { getWebSocketToken } from "@/lib/supabase/actions";
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { createBrowserClient } from "@supabase/ssr";
import type { SupabaseClient } from "@supabase/supabase-js";
import { proxyApiRequest, proxyFileUpload } from "./proxy-action";
import type {
AddUserCreditsResponse,
AnalyticsDetails,
@@ -27,6 +26,7 @@ import type {
GraphID,
GraphMeta,
GraphUpdateable,
HostScopedCredentials,
LibraryAgent,
LibraryAgentID,
LibraryAgentPreset,
@@ -62,7 +62,6 @@ import type {
User,
UserOnboarding,
UserPasswordCredentials,
HostScopedCredentials,
UsersBalanceHistoryResponse,
} from "./types";
@@ -521,8 +520,6 @@ export default class BackendAPI {
}
uploadStoreSubmissionMedia(file: File): Promise<string> {
const formData = new FormData();
formData.append("file", file);
return this._uploadFile("/store/submissions/media", file);
}
@@ -813,8 +810,48 @@ export default class BackendAPI {
const formData = new FormData();
formData.append("file", file);
// Use proxy server action for secure file upload
return await proxyFileUpload(path, formData, this.baseUrl);
if (isClient) {
return this._makeClientFileUpload(path, formData);
} else {
return this._makeServerFileUpload(path, formData);
}
}
private async _makeClientFileUpload(
path: string,
formData: FormData,
): Promise<string> {
// Dynamic import is required even for client-only functions because helpers.ts
// has server-only imports (like getServerSupabase) at the top level. Static imports
// would bundle server-only code into the client bundle, causing runtime errors.
const { buildClientUrl, parseErrorResponse, handleFetchError } =
await import("./helpers");
const uploadUrl = buildClientUrl(path);
const response = await fetch(uploadUrl, {
method: "POST",
body: formData,
credentials: "include",
});
if (!response.ok) {
const errorData = await parseErrorResponse(response);
throw handleFetchError(response, errorData);
}
return await response.text();
}
private async _makeServerFileUpload(
path: string,
formData: FormData,
): Promise<string> {
const { makeAuthenticatedFileUpload, buildServerUrl } = await import(
"./helpers"
);
const url = buildServerUrl(path);
return await makeAuthenticatedFileUpload(url, formData);
}
private async _request(
@@ -826,13 +863,62 @@ export default class BackendAPI {
console.debug(`${method} ${path} payload:`, payload);
}
// Always use proxy server action to not expose any auth tokens to the browser
return await proxyApiRequest({
if (isClient) {
return this._makeClientRequest(method, path, payload);
} else {
return this._makeServerRequest(method, path, payload);
}
}
private async _makeClientRequest(
method: string,
path: string,
payload?: Record<string, any>,
) {
// Dynamic import is required even for client-only functions because helpers.ts
// has server-only imports (like getServerSupabase) at the top level. Static imports
// would bundle server-only code into the client bundle, causing runtime errors.
const {
buildClientUrl,
buildUrlWithQuery,
parseErrorResponse,
handleFetchError,
} = await import("./helpers");
const payloadAsQuery = ["GET", "DELETE"].includes(method);
let url = buildClientUrl(path);
if (payloadAsQuery && payload) {
url = buildUrlWithQuery(url, payload);
}
const response = await fetch(url, {
method,
path,
payload,
baseUrl: this.baseUrl,
headers: {
"Content-Type": "application/json",
},
body: !payloadAsQuery && payload ? JSON.stringify(payload) : undefined,
credentials: "include",
});
if (!response.ok) {
const errorData = await parseErrorResponse(response);
throw handleFetchError(response, errorData);
}
return await response.json();
}
private async _makeServerRequest(
method: string,
path: string,
payload?: Record<string, any>,
) {
const { makeAuthenticatedRequest, buildServerUrl } = await import(
"./helpers"
);
const url = buildServerUrl(path);
return await makeAuthenticatedRequest(method, url, payload);
}
////////////////////////////////////////

View File

@@ -29,6 +29,42 @@ export function buildRequestUrl(
return url;
}
export function buildClientUrl(path: string): string {
return `/api/proxy/api${path}`;
}
export function buildServerUrl(path: string): string {
const baseUrl =
process.env.NEXT_PUBLIC_AGPT_SERVER_URL || "http://localhost:8006/api";
return `${baseUrl}${path}`;
}
export function buildUrlWithQuery(
url: string,
payload?: Record<string, any>,
): string {
if (!payload) return url;
const queryParams = new URLSearchParams(payload);
return `${url}?${queryParams.toString()}`;
}
export function handleFetchError(response: Response, errorData: any): ApiError {
return new ApiError(
errorData?.error || "Request failed",
response.status,
errorData,
);
}
export async function parseErrorResponse(response: Response): Promise<any> {
try {
return await response.json();
} catch {
return { error: response.statusText };
}
}
export async function getServerAuthToken(): Promise<string> {
const supabase = await getServerSupabase();