All files / src/services httpClient.ts

98.78% Statements 81/82
91.66% Branches 33/36
100% Functions 2/2
98.78% Lines 81/82

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  1x 1x     1x     1x   1x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x 11x   11x 11x   11x 12x 12x 12x 12x 12x 12x 1x 11x 12x 12x 12x 2x 1x 1x 10x 12x 12x   9x   12x 3x 1x 1x 1x 1x 2x 2x 3x 3x 3x 3x   6x 12x 12x 12x 12x 12x 3x 3x 1x 1x 1x 1x 2x 2x 2x 3x 3x 3x 3x 12x   1x 1x 1x 1x   1x 11x 11x 11x  
// Constants
import { ENV } from '@/constants';
import { STATUS_CODE, API_ERROR_MESSAGE } from '@shared/constants';
 
// Types
import { HttpMethod } from '@shared/types';
import type { HttpClientConfig, HttpResponse } from '@shared/types';
 
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
 
export const httpClient = async <T>(
  endpoint: string,
  {
    method = HttpMethod.GET,
    headers = {},
    body,
    token,
    retry,
    isFullResponse = false,
    baseURL = ENV.SPACE_API_ENDPOINT || '',
    ...rest
  }: HttpClientConfig = {},
): Promise<HttpResponse<T>> => {
  const attempts = retry?.attempts ?? 1;
  const delay = retry?.delay ?? 0;
  const retryStatus = retry?.statusCodes ?? [503];
 
  let tries = attempts;
  let lastError: unknown;
 
  while (tries > 0) {
    try {
      const res = await fetch(`${baseURL}${endpoint}`, {
        method,
        headers: {
          ...(token ? { Authorization: `Bearer ${token}` } : {}),
          ...(body && !(body instanceof FormData)
            ? { 'Content-Type': 'application/json' }
            : {}),
          ...headers,
        },
        body: body
          ? body instanceof FormData
            ? body
            : JSON.stringify(body)
          : undefined,
        ...rest,
      });
 
      const json = await res.json().catch(() => ({}));
 
      if (!res.ok) {
        if (tries > 1 && retryStatus.includes(res.status)) {
          tries--;
          await sleep(delay);
          continue;
        }
        return {
          data: null,
          error: json?.message || res.statusText,
          status: res.status,
        };
      }
 
      return {
        data: isFullResponse ? (json as T) : (json.data as T),
        error: null,
        status: res.status,
      };
    } catch (err) {
      lastError = err;
      if (tries > 1) {
        tries--;
        await sleep(delay);
        continue;
      }
      return {
        data: null,
        error:
          err instanceof Error ? err.message : API_ERROR_MESSAGE.NETWORK_ERROR,
        status: STATUS_CODE.INTERNAL_SERVER_ERROR,
      };
    }
  }
 
  return {
    data: null,
    error:
      lastError instanceof Error
        ? lastError.message
        : API_ERROR_MESSAGE.MAXIMUM_RETRY_ATTEMPTS,
    status: STATUS_CODE.INTERNAL_SERVER_ERROR,
  };
};