All files / src/api client.ts

97.61% Statements 41/42
94.44% Branches 17/18
80% Functions 4/5
97.61% Lines 41/42

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109                    36x                       36x 70x 70x   1x         36x     70x 70x 47x       70x 70x   4x       70x 70x 2x     70x               36x 48x   22x     22x 12x   12x   12x 12x 7x       4x     4x 4x     4x 4x   4x       3x 3x 3x 3x 3x 3x 3x 3x 3x       15x          
/**
 * API Client
 * Axios instance configured for SmoothSchedule API
 */
 
import axios, { AxiosError, InternalAxiosRequestConfig } from 'axios';
import { API_BASE_URL, getSubdomain } from './config';
import { getCookie } from '../utils/cookies';
 
// Create axios instance
const apiClient = axios.create({
  baseURL: API_BASE_URL,
  headers: {
    'Content-Type': 'application/json',
  },
  withCredentials: true, // For CORS with credentials
});
 
/**
 * Get sandbox mode from localStorage
 * This is set by the SandboxContext when mode changes
 */
const getSandboxMode = (): boolean => {
  try {
    return localStorage.getItem('sandbox_mode') === 'true';
  } catch {
    return false;
  }
};
 
// Request interceptor - add auth token, business subdomain, and sandbox mode
apiClient.interceptors.request.use(
  (config: InternalAxiosRequestConfig) => {
    // Add business subdomain header if on business site
    const subdomain = getSubdomain();
    if (subdomain && subdomain !== 'platform') {
      config.headers['X-Business-Subdomain'] = subdomain;
    }
 
    // Add auth token if available (from cookie)
    const token = getCookie('access_token');
    if (token) {
      // Use 'Token' prefix for Django REST Framework Token Authentication
      config.headers['Authorization'] = `Token ${token}`;
    }
 
    // Add sandbox mode header if in test mode
    const isSandbox = getSandboxMode();
    if (isSandbox) {
      config.headers['X-Sandbox-Mode'] = 'true';
    }
 
    return config;
  },
  (error) => {
    return Promise.reject(error);
  }
);
 
// Response interceptor - handle errors and token refresh
apiClient.interceptors.response.use(
  (response) => response,
  async (error: AxiosError) => {
    const originalRequest = error.config as InternalAxiosRequestConfig & { _retry?: boolean };
 
    // Handle 401 Unauthorized - token expired
    if (error.response?.status === 401 && !originalRequest._retry) {
      originalRequest._retry = true;
 
      try {
        // Try to refresh token (from cookie)
        const refreshToken = getCookie('refresh_token');
        if (refreshToken) {
          const response = await axios.post(`${API_BASE_URL}/auth/refresh/`, {
            refresh: refreshToken,
          });
 
          const { access } = response.data;
 
          // Import setCookie dynamically to avoid circular dependency
          const { setCookie } = await import('../utils/cookies');
          setCookie('access_token', access, 7);
 
          // Retry original request with new token
          Eif (originalRequest.headers) {
            originalRequest.headers['Authorization'] = `Bearer ${access}`;
          }
          return apiClient(originalRequest);
        }
      } catch (refreshError) {
        // Refresh failed - clear tokens and redirect to login on root domain
        const { deleteCookie } = await import('../utils/cookies');
        const { getBaseDomain } = await import('../utils/domain');
        deleteCookie('access_token');
        deleteCookie('refresh_token');
        const protocol = window.location.protocol;
        const baseDomain = getBaseDomain();
        const port = window.location.port ? `:${window.location.port}` : '';
        window.location.href = `${protocol}//${baseDomain}${port}/login`;
        return Promise.reject(refreshError);
      }
    }
 
    return Promise.reject(error);
  }
);
 
export default apiClient;