import axios, { type InternalAxiosRequestConfig } from "axios"; import { isLocale } from "../translations/config"; import { authBridge } from "./auth-bridge"; import { getClientCookie } from "./cookies"; const PROXY_PATH_PARAM = "__proxyPath"; function isAbsoluteUrl(url: string) { return /^[a-z][a-z\d+\-.]*:\/\//i.test(url); } function shouldUseProxy() { if (process.env.NEXT_PUBLIC_DISABLE_PROXY === "true") { return false; } if (typeof window === "undefined") { return false; } return true; } function withProxyPathParam( params: InternalAxiosRequestConfig["params"], proxyPath: string, ) { if (params instanceof URLSearchParams) { const nextParams = new URLSearchParams(params); nextParams.set(PROXY_PATH_PARAM, proxyPath); return nextParams; } if (typeof params === "string") { const nextParams = new URLSearchParams(params); nextParams.set(PROXY_PATH_PARAM, proxyPath); return nextParams; } return { ...(params && typeof params === "object" ? params : {}), [PROXY_PATH_PARAM]: proxyPath, }; } export function getApiRequestUrl(path: string) { if (!isAbsoluteUrl(path) && shouldUseProxy()) { const searchParams = new URLSearchParams({ [PROXY_PATH_PARAM]: path, }); return `/api/proxy?${searchParams.toString()}`; } return `${process.env.NEXT_PUBLIC_API_BASE_URL}${path}`; } export function resolveMediaUrl(url: string | null | undefined): string | null { if (!url || typeof url !== "string") { return null; } const trimmed = url.trim(); if (!trimmed) { return null; } // Blob and data URLs are generated in the current browser session and work immediately if (trimmed.startsWith("blob:") || trimmed.startsWith("data:")) { return trimmed; } if (shouldUseProxy()) { if (isAbsoluteUrl(trimmed)) { try { const parsed = new URL(trimmed); const apiBase = process.env.NEXT_PUBLIC_API_BASE_URL; let isBackendHost = false; if ( parsed.hostname === "127.0.0.1" || parsed.hostname === "localhost" || parsed.port === "8000" || parsed.port === "8001" ) { isBackendHost = true; } else if (apiBase) { try { const apiParsed = new URL(apiBase); if (parsed.host === apiParsed.host) { isBackendHost = true; } } catch { // Ignore URL parsing error } } if ( isBackendHost || parsed.pathname.startsWith("/static/") || parsed.pathname.startsWith("/media/") ) { const proxyPath = `${parsed.pathname}${parsed.search}`; const searchParams = new URLSearchParams({ [PROXY_PATH_PARAM]: proxyPath, }); return `/api/proxy?${searchParams.toString()}`; } } catch { // Fall through } } else { const normalizedPath = trimmed.startsWith("/") ? trimmed : `/${trimmed}`; const searchParams = new URLSearchParams({ [PROXY_PATH_PARAM]: normalizedPath, }); return `/api/proxy?${searchParams.toString()}`; } } if (isAbsoluteUrl(trimmed)) { return trimmed; } const base = process.env.NEXT_PUBLIC_API_BASE_URL || ""; const normalized = trimmed.startsWith("/") ? trimmed : `/${trimmed}`; return `${base}${normalized}`; } export const http = axios.create({ baseURL: shouldUseProxy() ? "/api/proxy" : process.env.NEXT_PUBLIC_API_BASE_URL, headers: { Accept: "application/json", "Content-Type": "application/json", }, withCredentials: true, }); http.interceptors.request.use((config) => { if (shouldUseProxy() && config.url && !isAbsoluteUrl(config.url)) { config.params = withProxyPathParam(config.params, config.url); config.url = ""; } const stripNoToken = (v: string | null) => (v && v !== "NO_TOKEN" ? v : null); const token = authBridge.getToken() ?? stripNoToken(getClientCookie("HABIB_TOKEN")) ?? stripNoToken(getClientCookie("habib_token")); if (token) { config.headers.Authorization = `Token ${token}`; } const pathSegment = typeof window !== "undefined" ? window.location.pathname.split("/")[1] : undefined; const docLang = typeof document !== "undefined" ? document.documentElement.lang : undefined; const cookieLanguage = getClientCookie("HABIB_LANGUAGE") ?? getClientCookie("habib_language") ?? undefined; const lang = isLocale(pathSegment) ? pathSegment : isLocale(cookieLanguage) ? cookieLanguage : isLocale(docLang) ? docLang : "en"; config.headers["Accept-Language"] = lang; config.headers["X-User-Language"] = lang; return config; }); http.interceptors.response.use( (response) => response, (error) => Promise.reject(error), );