Files
bifrost/ui/lib/store/apis/baseApi.ts
Beyhan Oğur 880f412e2c first commit
2026-04-26 21:52:23 +03:00

198 lines
5.0 KiB
TypeScript

import { IS_ENTERPRISE } from "@/lib/constants/config";
import { BifrostErrorResponse } from "@/lib/types/config";
import { getApiBaseUrl } from "@/lib/utils/port";
import { createBaseQueryWithRefresh } from "@enterprise/lib/store/utils/baseQueryWithRefresh";
import { clearOAuthStorage } from "@enterprise/lib/store/utils/tokenManager";
import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react";
// Auth tokens are now stored in HTTP-only cookies (set by server)
// No client-side token needed — handled by credentials: "include"
export const getTokenFromStorage = (): Promise<string | null> => {
return Promise.resolve(null);
};
// Helper function to set auth token
// Non-enterprise: no-op — auth relies on HTTPOnly cookies set by the server
// Enterprise: handled separately via tokenManager
export const setAuthToken = (_token: string | null) => {
// Non-enterprise auth is cookie-based; no client-side token storage needed.
// Enterprise token management is handled by the tokenManager module.
};
// Helper function to clear all auth-related storage
export const clearAuthStorage = () => {
if (typeof window === "undefined") {
return;
}
try {
// Clear traditional auth token
localStorage.removeItem("bifrost-auth-token");
// Clear enterprise OAuth tokens using tokenManager
if (IS_ENTERPRISE) {
clearOAuthStorage();
}
} catch (error) {
console.error("Error clearing auth storage:", error);
}
};
// Define the base query with authentication headers
const baseQuery = fetchBaseQuery({
baseUrl: getApiBaseUrl(),
credentials: "include",
prepareHeaders: async (headers) => {
headers.set("Content-Type", "application/json");
// Automatically include token from localStorage in Authorization header
const token = await getTokenFromStorage();
if (token) {
headers.set("Authorization", `Bearer ${token}`);
}
return headers;
},
});
// Wrap base query with enterprise refresh logic (or passthrough for non-enterprise)
const baseQueryWithRefresh = createBaseQueryWithRefresh(baseQuery);
// Enhanced base query with error handling
const baseQueryWithErrorHandling: typeof baseQueryWithRefresh = async (args: any, api: any, extraOptions: any) => {
// First apply refresh logic (enterprise-specific, handles 401)
const result = await baseQueryWithRefresh(args, api, extraOptions);
// Then handle other error types
if (result.error) {
const error = result.error as any;
// Handle 401 for non-enterprise (no refresh available)
if (error?.status === 401 && !IS_ENTERPRISE) {
clearAuthStorage();
if (typeof window !== "undefined" && !window.location.pathname.includes("/login")) {
window.location.href = "/login";
}
return result;
}
// Handle specific error types
if (error?.status === "FETCH_ERROR") {
// Network error
return {
...result,
error: {
...error,
data: {
error: {
message: "Network error: Unable to connect to the server",
},
},
},
};
}
// Handle other errors with proper BifrostErrorResponse format
if (error?.data) {
const errorData = error.data as BifrostErrorResponse;
if (errorData.error?.message) {
return result;
}
}
// Fallback error message
return {
...result,
error: {
...error,
data: {
error: {
message: "An unexpected error occurred",
},
},
},
};
}
return result;
};
// Create the base API
export const baseApi = createApi({
reducerPath: "api",
baseQuery: baseQueryWithErrorHandling,
tagTypes: [
"Logs",
"MCPLogs",
"Providers",
"MCPClients",
"Config",
"CacheConfig",
"VirtualKeys",
"Teams",
"Customers",
"Budgets",
"RateLimits",
"UsageStats",
"DebugStats",
"HealthCheck",
"DBKeys",
"ProviderKeys",
"Models",
"BaseModels",
"ModelConfigs",
"ProviderGovernance",
"Plugins",
"SCIMProviders",
"User",
"Guardrails",
"ClusterNodes",
"Users",
"GuardrailRules",
"Roles",
"Resources",
"Operations",
"Permissions",
"APIKeys",
"OAuth2Config",
"RoutingRules",
"PricingOverrides",
"MCPToolGroups",
"AuditLogs",
"UserGovernance",
"LargePayloadConfig",
"Folders",
"Prompts",
"Versions",
"Sessions",
"AccessProfiles",
"BusinessUnits",
"PromptDeployments",
],
endpoints: () => ({}),
});
// Helper function to extract error message from RTK Query error
export const getErrorMessage = (error: unknown): string => {
if (error === undefined || error === null) {
return "An unexpected error occurred";
}
if (error instanceof Error) {
return error.message;
}
if (
typeof error === "object" &&
error &&
"data" in error &&
error.data &&
typeof error.data === "object" &&
"error" in error.data &&
error.data.error &&
typeof error.data.error === "object" &&
"message" in error.data.error &&
typeof error.data.error.message === "string"
) {
return error.data.error.message.charAt(0).toUpperCase() + error.data.error.message.slice(1);
}
if (typeof error === "object" && error && "message" in error && typeof error.message === "string") {
return error.message;
}
return "An unexpected error occurred";
};