All files / src/services httpClient.ts

85.36% Statements 70/82
78.12% Branches 25/32
100% Functions 2/2
85.36% Lines 70/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 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x 8x   8x 8x   8x 9x 9x 9x 9x 9x 9x   9x 9x 9x 9x 1x 1x   8x 9x 9x   7x   9x 3x 1x 1x 1x 1x 2x 2x 3x 3x 3x 3x   4x 9x 9x 9x 9x 9x 2x 2x         2x 2x 2x 2x 2x 2x 2x 9x               8x 8x 8x  
// 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,
  };
};