mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-04-08 03:00:28 -04:00
fix(frontend) : Update server-side mutator to bypass proxy (#10523)
This PR helps us bypass the proxy server in server-side requests, allowing us to directly send requests to the backend and reduce latency. ### Changes 🏗️ - Introduced server-side detection to dynamically set the base URL for API requests. - Added error handling for server-side requests to log failures and throw errors appropriately. - Updated header management to include authentication tokens when applicable. ### Checklist 📋 - [x] I have clearly listed my changes in the PR description - [x] I have made a test plan - [x] I have tested my changes according to the test plan: - [x] All E2E tests are working. - [x] I have manually checked the server-side and client-side components, and both are working perfectly.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { isServerSide } from "@/lib/utils/is-server-side";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function AuthErrorPage() {
|
||||
@@ -9,7 +10,7 @@ export default function AuthErrorPage() {
|
||||
|
||||
useEffect(() => {
|
||||
// This code only runs on the client side
|
||||
if (typeof window !== "undefined") {
|
||||
if (!isServerSide()) {
|
||||
const hash = window.location.hash.substring(1); // Remove the leading '#'
|
||||
const params = new URLSearchParams(hash);
|
||||
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import {
|
||||
createRequestHeaders,
|
||||
getServerAuthToken,
|
||||
} from "@/lib/autogpt-server-api/helpers";
|
||||
import { isServerSide } from "@/lib/utils/is-server-side";
|
||||
|
||||
const FRONTEND_BASE_URL =
|
||||
process.env.NEXT_PUBLIC_FRONTEND_BASE_URL || "http://localhost:3000";
|
||||
const API_PROXY_BASE_URL = `${FRONTEND_BASE_URL}/api/proxy`; // Sending request via nextjs Server
|
||||
|
||||
const getBaseUrl = (): string => {
|
||||
if (!isServerSide()) {
|
||||
return API_PROXY_BASE_URL;
|
||||
} else {
|
||||
return (
|
||||
process.env.NEXT_PUBLIC_AGPT_SERVER_BASE_URL || "http://localhost:8006"
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const getBody = <T>(c: Response | Request): Promise<T> => {
|
||||
const contentType = c.headers.get("content-type");
|
||||
|
||||
@@ -30,11 +46,12 @@ export const customMutator = async <T = any>(
|
||||
| "DELETE"
|
||||
| "PATCH";
|
||||
const data = requestOptions.body;
|
||||
const headers: Record<string, string> = {
|
||||
let headers: Record<string, string> = {
|
||||
...((requestOptions.headers as Record<string, string>) || {}),
|
||||
};
|
||||
|
||||
const isFormData = data instanceof FormData;
|
||||
const contentType = isFormData ? "multipart/form-data" : "application/json";
|
||||
|
||||
// Currently, only two content types are handled here: application/json and multipart/form-data
|
||||
if (!isFormData && data && !headers["Content-Type"]) {
|
||||
@@ -45,13 +62,41 @@ export const customMutator = async <T = any>(
|
||||
? "?" + new URLSearchParams(params).toString()
|
||||
: "";
|
||||
|
||||
const response = await fetch(`${API_PROXY_BASE_URL}${url}${queryString}`, {
|
||||
const baseUrl = getBaseUrl();
|
||||
|
||||
// The caching in React Query in our system depends on the url, so the base_url could be different for the server and client sides.
|
||||
const fullUrl = `${baseUrl}${url}${queryString}`;
|
||||
|
||||
if (isServerSide()) {
|
||||
try {
|
||||
const token = await getServerAuthToken();
|
||||
const authHeaders = createRequestHeaders(token, !!data, contentType);
|
||||
headers = { ...headers, ...authHeaders };
|
||||
} catch (error) {
|
||||
console.warn("Failed to get server auth token:", error);
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(fullUrl, {
|
||||
...requestOptions,
|
||||
method,
|
||||
headers,
|
||||
body: data,
|
||||
});
|
||||
|
||||
// Error handling for server-side requests
|
||||
// We do not need robust error handling for server-side requests; we only need to log the error message and throw the error.
|
||||
// What happens if the server-side request fails?
|
||||
// 1. The error will be logged in the terminal, then.
|
||||
// 2. The error will be thrown, so the cached data for this particular queryKey will be empty, then.
|
||||
// 3. The client-side will send the request again via the proxy. If it fails again, the error will be handled on the client side.
|
||||
// 4. If the request succeeds on the server side, the data will be cached, and the client will use it instead of sending a request to the proxy.
|
||||
|
||||
if (!response.ok && isServerSide()) {
|
||||
console.error("Request failed on server side", response);
|
||||
throw new Error(`Request failed with status ${response.status}`);
|
||||
}
|
||||
|
||||
const response_data = await getBody<T>(response);
|
||||
|
||||
return {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { isServerSide } from "@/lib/utils/is-server-side";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
|
||||
export interface TurnstileProps {
|
||||
@@ -31,7 +32,7 @@ export function Turnstile({
|
||||
|
||||
// Load the Turnstile script
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !shouldRender) return;
|
||||
if (isServerSide() || !shouldRender) return;
|
||||
|
||||
// Skip if already loaded
|
||||
if (window.turnstile) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isServerSide } from "@/lib/utils/is-server-side";
|
||||
import { debounce } from "lodash";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
|
||||
@@ -24,7 +25,7 @@ export const useInfiniteScroll = ({
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
const handleScroll = useCallback(() => {
|
||||
if (containerRef.current && typeof window !== "undefined") {
|
||||
if (containerRef.current && !isServerSide()) {
|
||||
const container = containerRef.current;
|
||||
const { bottom } = container.getBoundingClientRect();
|
||||
const { innerHeight } = window;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useCallback, useEffect } from "react";
|
||||
import { verifyTurnstileToken } from "@/lib/turnstile";
|
||||
import { BehaveAs, getBehaveAs } from "@/lib/utils";
|
||||
import { isServerSide } from "@/lib/utils/is-server-side";
|
||||
|
||||
interface UseTurnstileOptions {
|
||||
action?: string;
|
||||
@@ -79,12 +80,7 @@ export function useTurnstile({
|
||||
setError(null);
|
||||
|
||||
// Only reset the actual Turnstile widget if it exists and shouldRender is true
|
||||
if (
|
||||
shouldRender &&
|
||||
typeof window !== "undefined" &&
|
||||
window.turnstile &&
|
||||
widgetId
|
||||
) {
|
||||
if (shouldRender && !isServerSide() && window.turnstile && widgetId) {
|
||||
try {
|
||||
window.turnstile.reset(widgetId);
|
||||
} catch (err) {
|
||||
|
||||
@@ -66,8 +66,9 @@ import type {
|
||||
UserPasswordCredentials,
|
||||
UsersBalanceHistoryResponse,
|
||||
} from "./types";
|
||||
import { isServerSide } from "../utils/is-server-side";
|
||||
|
||||
const isClient = typeof window !== "undefined";
|
||||
const isClient = !isServerSide();
|
||||
|
||||
export default class BackendAPI {
|
||||
private baseUrl: string;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { isServerSide } from "../utils/is-server-side";
|
||||
import BackendAPI from "./client";
|
||||
import React, { createContext, useMemo } from "react";
|
||||
|
||||
@@ -19,10 +20,7 @@ export function BackendAPIProvider({
|
||||
}): React.ReactNode {
|
||||
const api = useMemo(() => new BackendAPI(), []);
|
||||
|
||||
if (
|
||||
process.env.NEXT_PUBLIC_BEHAVE_AS == "LOCAL" &&
|
||||
typeof window !== "undefined"
|
||||
) {
|
||||
if (process.env.NEXT_PUBLIC_BEHAVE_AS == "LOCAL" && !isServerSide()) {
|
||||
window.api = api; // Expose the API globally for debugging purposes
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
|
||||
import { Key, storage } from "@/services/storage/local-storage";
|
||||
import { isServerSide } from "../utils/is-server-side";
|
||||
|
||||
export class ApiError extends Error {
|
||||
public status: number;
|
||||
@@ -179,7 +180,7 @@ function isAuthenticationError(
|
||||
}
|
||||
|
||||
function isLogoutInProgress(): boolean {
|
||||
if (typeof window === "undefined") return false;
|
||||
if (isServerSide()) return false;
|
||||
|
||||
try {
|
||||
// Check if logout was recently triggered
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { type CookieOptions } from "@supabase/ssr";
|
||||
import { SupabaseClient } from "@supabase/supabase-js";
|
||||
import { Key, storage } from "@/services/storage/local-storage";
|
||||
import { isServerSide } from "../utils/is-server-side";
|
||||
|
||||
export const PROTECTED_PAGES = [
|
||||
"/monitor",
|
||||
@@ -82,7 +83,7 @@ export function setupSessionEventListeners(
|
||||
onFocus: () => void,
|
||||
onStorageChange: (e: StorageEvent) => void,
|
||||
): EventListeners {
|
||||
if (typeof window === "undefined" || typeof document === "undefined") {
|
||||
if (isServerSide() || typeof document === "undefined") {
|
||||
return { cleanup: () => {} };
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
export const isServerSide = (): boolean => {
|
||||
return typeof window === "undefined";
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
import { isServerSide } from "@/lib/utils/is-server-side";
|
||||
import * as Sentry from "@sentry/nextjs";
|
||||
|
||||
export enum Key {
|
||||
@@ -8,7 +9,7 @@ export enum Key {
|
||||
}
|
||||
|
||||
function get(key: Key) {
|
||||
if (typeof window === "undefined") {
|
||||
if (isServerSide()) {
|
||||
Sentry.captureException(new Error("Local storage is not available"));
|
||||
return;
|
||||
}
|
||||
@@ -21,7 +22,7 @@ function get(key: Key) {
|
||||
}
|
||||
|
||||
function set(key: Key, value: string) {
|
||||
if (typeof window === "undefined") {
|
||||
if (isServerSide()) {
|
||||
Sentry.captureException(new Error("Local storage is not available"));
|
||||
return;
|
||||
}
|
||||
@@ -29,7 +30,7 @@ function set(key: Key, value: string) {
|
||||
}
|
||||
|
||||
function clean(key: Key) {
|
||||
if (typeof window === "undefined") {
|
||||
if (isServerSide()) {
|
||||
Sentry.captureException(new Error("Local storage is not available"));
|
||||
return;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user