Axios Original

 avatar
FadadOussama
typescript
10 months ago
7.3 kB
14
Indexable
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from "axios";
import { cookies } from "next/headers";
import { COOKIE_CONFIG } from "@/constants/cookies";

// Create axios instance with base configuration
const createAxiosInstance = (): AxiosInstance => {
  const instance = axios.create({
    baseURL: process.env.NAREVA_URL,
    timeout: 30000,
  });

  return instance;
};

// Server-side axios instance
export const apiClient = createAxiosInstance();

// Request interceptor to add auth token
apiClient.interceptors.request.use(
  async (config) => {
    try {
      const cookieStore = await cookies();
      const token = cookieStore.get(COOKIE_CONFIG.AUTH_TOKEN.name);

      if (token) {
        config.headers.Authorization = `Bearer ${token.value}`;
      }

      // Set Content-Type based on data type
      if (config.data instanceof FormData) {
        // Let axios handle FormData Content-Type automatically (it will set multipart/form-data with boundary)
        delete config.headers["Content-Type"];
      } else {
        // Set JSON Content-Type for other data types
        config.headers["Content-Type"] = "application/json";
      }
    } catch (error) {
      console.error("Error getting auth token:", error);
    }

    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);

// Helper function to clear auth cookies and redirect to login
async function clearAuthCookiesAndRedirect() {
  try {
    const cookieStore = await cookies();

    // Clear auth-related cookies
    cookieStore.delete(COOKIE_CONFIG.AUTH_TOKEN.name);
    cookieStore.delete(COOKIE_CONFIG.REFRESH_TOKEN.name);
    cookieStore.delete(COOKIE_CONFIG.USER_DATA.name);

    console.log("Auth cookies cleared");

    // Redirect to login - use window.location for client-side or throw error for server-side
    if (typeof window !== "undefined") {
      window.location.href = "/login";
    } else {
      // For server-side, we'll let the middleware handle the redirect
      console.log("Server-side auth failure - middleware will handle redirect");
    }
  } catch (error) {
    console.error("Error clearing cookies:", error);
  }
}

// Response interceptor to handle token refresh
apiClient.interceptors.response.use(
  (response: AxiosResponse) => {
    return response;
  },
  async (error) => {
    const originalRequest = error.config;

    // If error is 401 and we haven't already tried to refresh
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;

      try {
        const refreshSuccess = await refreshToken();

        if (refreshSuccess) {
          // Get the new token and retry the original request
          const cookieStore = await cookies();
          const newToken = cookieStore.get(COOKIE_CONFIG.AUTH_TOKEN.name);

          if (newToken) {
            originalRequest.headers.Authorization = `Bearer ${newToken.value}`;
            return apiClient(originalRequest);
          }
        } else {
          // Refresh token failed - clear cookies and redirect
          console.error("Refresh token failed, redirecting to login");

          await clearAuthCookiesAndRedirect();
          return Promise.reject(
            new Error("Session expired. Please login again.")
          );
        }
      } catch (refreshError) {
        console.error("Token refresh failed:", refreshError);

        await clearAuthCookiesAndRedirect();
        return Promise.reject(
          new Error("Authentication failed. Please login again.")
        );
      }
    }

    // If it's a 401 error after refresh attempt, redirect to login
    if (error.response?.status === 401) {
      console.error("401 Unauthorized - redirecting to login");

      await clearAuthCookiesAndRedirect();
      return Promise.reject(
        new Error("Authentication failed. Please login again.")
      );
    }

    return Promise.reject(error);
  }
);

// Refresh token function
async function refreshToken(): Promise<boolean> {
  try {
    const cookieStore = await cookies();
    const accessToken = cookieStore.get(COOKIE_CONFIG.AUTH_TOKEN.name);
    const refreshTokenValue = cookieStore.get(COOKIE_CONFIG.REFRESH_TOKEN.name);

    if (!accessToken || !refreshTokenValue) {
      console.error("No access token or refresh token found");
      return false;
    }

    const response = await axios.post(
      `${process.env.NAREVA_URL}/api/Shared/Authenticate/RefreshToken`,
      {
        accessToken: accessToken.value,
        refreshToken: refreshTokenValue.value,
      },
      {
        headers: {
          "Content-Type": "application/json",
        },
      }
    );

    if (response.data.success) {
      // Update tokens in cookies
      cookieStore.set(COOKIE_CONFIG.AUTH_TOKEN.name, response.data.data.token, {
        ...COOKIE_CONFIG.AUTH_TOKEN,
      });

      if (response.data.data.refreshToken) {
        cookieStore.set(
          COOKIE_CONFIG.REFRESH_TOKEN.name,
          response.data.data.refreshToken,
          {
            ...COOKIE_CONFIG.REFRESH_TOKEN,
          }
        );
      }

      return true;
    }

    return false;
  } catch (error) {
    console.error("Token refresh failed:", error);
    return false;
  }
}

// Helper function for making API requests with better error handling
export const makeRequest = async <T = any>(
  config: AxiosRequestConfig
): Promise<T> => {
  try {
    const response = await apiClient(config);
    return response.data;
  } catch (error) {
    if (axios.isAxiosError(error)) {
      // Handle different error types
      if (error.response) {
        console.error("API Error:", error.response.data);

        // Server responded with error status
        throw new Error(
          error.response.data?.title || `HTTP Error: ${error.response.status}`
        );
      } else if (error.request) {
        // Request was made but no response received
        throw new Error("Network error: No response from server");
      } else {
        // Something else happened
        throw new Error(`Request error: ${error.message}`);
      }
    }
    throw error;
  }
};

// Convenience methods for different HTTP verbs
export const apiGet = <T = any>(
  url: string,
  config?: AxiosRequestConfig
): Promise<T> => {
  return makeRequest<T>({ ...config, method: "GET", url });
};

export const apiPost = <T = any>(
  url: string,
  data?: any,
  config?: AxiosRequestConfig
): Promise<T> => {
  return makeRequest<T>({ ...config, method: "POST", url, data });
};

export const apiPut = <T = any>(
  url: string,
  data?: any,
  config?: AxiosRequestConfig
): Promise<T> => {
  return makeRequest<T>({ ...config, method: "PUT", url, data });
};

export const apiDelete = <T = any>(
  url: string,
  config?: AxiosRequestConfig
): Promise<T> => {
  return makeRequest<T>({ ...config, method: "DELETE", url });
};

export const apiPatch = <T = any>(
  url: string,
  data?: any,
  config?: AxiosRequestConfig
): Promise<T> => {
  return makeRequest<T>({ ...config, method: "PATCH", url, data });
};

export default apiClient;
Editor is loading...
Leave a Comment