문의 사항이 생겨 이슈 남깁니다.
import returnFetch, {
ReturnFetch,
FetchArgs,
ReturnFetchDefaultOptions,
} from 'return-fetch';
import { get, set } from 'lodash';
import qs from 'qs';
import { useCustomerStore } from '@/store/customer';
import { RequestParams } from './types';
import logger from '@/utils/logger';
// req body를 json으로 받고, res body를 json으로 반환하는 fetch
type JsonRequestInit = Omit<NonNullable<FetchArgs[1]>, 'body'> & {
body?: object;
queryParams?: Record<string, unknown>;
};
export type ResponseGenericBody<T> = Omit<
Awaited<ReturnType<typeof fetch>>,
keyof Body | 'clone'
> & {
body: T;
};
export type JsonResponse<T> = T extends object
? ResponseGenericBody<T>
: ResponseGenericBody<unknown>;
const parseJsonSafely = (text: string): object | string => {
try {
return JSON.parse(text);
} catch (e) {
if ((e as Error).name !== 'SyntaxError') {
throw e;
}
return text.trim();
}
};
export const returnFetchJson = (args?: ReturnFetchDefaultOptions) => {
const fetch = returnFetch(args);
return async <T>(
url: FetchArgs[0],
init?: JsonRequestInit
): Promise<JsonResponse<T>> => {
const response = await fetch(url, {
...init,
body: init?.body && JSON.stringify(init.body),
});
const body = parseJsonSafely(await response.text()) as T;
return {
headers: response.headers,
ok: response.ok,
redirected: response.redirected,
status: response.status,
statusText: response.statusText,
type: response.type,
url: response.url,
body,
} as JsonResponse<T>;
};
};
// reposne가 400이상의 status를 받는 경우 에러 throw
const returnFetchThrowingErrorByStatusCode: ReturnFetch = (args) =>
returnFetch({
...args,
interceptors: {
response: async (response) => {
if (response.status >= 400) {
throw await response.text().then(Error);
}
return response;
},
},
});
export const client = returnFetchJson({
fetch: returnFetchThrowingErrorByStatusCode({
baseUrl: process.env.NEXT_PUBLIC_API_URL,
headers: { Accept: 'application/json' },
querySerializer: (queryParams) => {
return qs.stringify(queryParams, { arrayFormat: 'brackets' });
},
interceptors: {
request: async (requestArgs) => {
const [url, init] = requestArgs;
logger('--- Request 시작 ---');
logger(`> Request url: ', ${url}`);
logger(`> Request: ', ${init}`);
const { authorization } = useCustomerStore.getState();
logger(`> Authorization: ', ${authorization}`);
if (authorization) {
logger('> Set Authorization: ', authorization);
set(requestArgs, 'headers.authorization', authorization);
}
set(requestArgs, 'headers.service-name', 'CUSTOMER_APP');
logger('--- Request 종료 ---');
return requestArgs;
},
response: async (response, requestArgs) => {
const { headers, url, status, statusText } = response;
const [_, requestInit] = requestArgs;
const apiUrl = `${process.env.NEXT_PUBLIC_API_URL}${url}`;
const authorization = get(headers, 'authorization');
logger('--- Response 시작 ---');
if (authorization) {
logger('## Save authorization via response: ', authorization);
useCustomerStore.getState().saveAuthorization(authorization);
}
logger(`< Response [${requestInit?.method}]: ${apiUrl}`);
logger(`< Response [${status}]: ${statusText ?? ''}`);
logger(`< Response Headers: ${JSON.stringify(headers)}`);
logger(response);
logger('--- Response 종료 ---');
return response;
},
},
}),
});
const fetchResponseOnRejected = (error: Error) => {
const method = get(error, 'config.method');
const url = `${process.env.NEXT_PUBLIC_API_URL}${get(error, 'config.url')}`;
logger(`< Response Error [${method}]: ${url}`);
logger(`< Response Error: `, JSON.stringify(get(error, 'response.error')));
if (get(error, 'response.status') === 403) {
useCustomerStore.getState().clearCustomer();
}
logger('--- Response 에러 ---');
return Promise.reject(error);
};
export const request = async <T>({
method,
url,
queryParams,
requestBody,
isMultipart,
}: RequestParams) => {
let headers = {};
if (isMultipart) {
headers = {
'Content-Type': 'multipart/form-data',
};
}
switch (method) {
case 'get':
return client(url, { method, headers, queryParams }).catch(
fetchResponseOnRejected
);
case 'post':
return client(url, { method, headers, requestBody, queryParams }).catch(
fetchResponseOnRejected
);
case 'put':
return client(url, { method, headers, requestBody, queryParams }).catch(
fetchResponseOnRejected
);
case 'delete':
return client(url, { method, headers, requustBody }).catch(
fetchResponseOnRejected
);
default:
return Promise.reject(new Error('Invalid method'));
}
};