Files
web-temp/layers/utils/apiUtil.ts

189 lines
4.1 KiB
TypeScript

/**
* API 유틸리티 함수
* @description API 호출에 필요한 유틸리티 함수를 제공합니다.
*/
import type {
HttpMethod,
FetchOptions,
FetchRequestOptions,
FetchErrorObject,
RequestObject,
} from '#layers/types/api/common'
/**
* 로딩 상태를 시작하는 헬퍼 함수
*/
const startLoading = (
loadingStore: ReturnType<typeof useLoadingStore>,
loading: FetchOptions['loading']
) => {
if (!loadingStore) return
if (typeof loading === 'object' && loading.localId) {
loadingStore.startLocalLoading(loading.localId)
}
}
/**
* 로딩 상태를 종료하는 헬퍼 함수
*/
const stopLoading = (
loadingStore: ReturnType<typeof useLoadingStore>,
loading: FetchOptions['loading']
) => {
if (!loadingStore) return
if (typeof loading === 'object' && loading.localId) {
loadingStore.stopLocalLoading(loading.localId)
return
}
if (loading === true) {
loadingStore.finishApiLoading()
}
}
/**
* 요청 옵션을 구성하는 헬퍼 함수
*/
const buildRequestOptions = (
method: HttpMethod,
url: string,
options: FetchOptions
): FetchRequestOptions => {
let stoveGameId = ''
let callerDetail = ''
if (import.meta.client) {
try {
const gameDataStore = useGameDataStore()
stoveGameId = gameDataStore.gameData?.game_id || ''
} catch {
stoveGameId = ''
}
callerDetail = useCookie('sgs_da_uuid').value || ''
}
const requestOptions: FetchRequestOptions = {
method,
headers: {
'Content-Type': 'application/json;charset=UTF-8',
},
}
// 쿼리 파라미터 추가
if (options.query) {
requestOptions.query = options.query
}
// 플랫폼 환경 API 호출 시 Caller 헤더 추가
if (url.includes('.onstove.com') || url.includes('.gate8.com')) {
requestOptions.headers = {
...requestOptions.headers,
'Caller-Id': stoveGameId as string,
'Caller-Detail': callerDetail as string,
}
}
// 사용자 정의 헤더 추가
if (options.headers) {
requestOptions.headers = {
...requestOptions.headers,
...(options.headers as Record<string, string>),
}
}
// 본문 데이터 추가
if (options.body) {
requestOptions.body = options.body
}
// 캐시 키 추가
if (options.key) {
requestOptions.key = options.key
}
return requestOptions
}
/**
* 공통 API 호출 함수 (리팩토링된 버전)
*
* @param method - HTTP 메소드
* @param url - API URL
* @param options - 요청 옵션
* @returns API 응답 데이터
*/
export const commonFetch = async (
method: HttpMethod = 'GET',
url: string,
options: FetchOptions = {}
): Promise<any> => {
let loadingStore = null
if (import.meta.client) {
try {
loadingStore = useLoadingStore()
} catch {
loadingStore = null
}
}
startLoading(loadingStore, options.loading)
try {
const requestOptions = buildRequestOptions(method, url, options)
return await $fetch(url, requestOptions)
} catch (error) {
console.error('[Exception] apiUtil.commonFetch:', error)
const fetchError = error as FetchErrorObject
return (
fetchError.data || {
code: fetchError.statusCode,
message: fetchError.statusMessage,
}
)
} finally {
stopLoading(loadingStore, options.loading)
}
}
/**
* 사용자 IP 조회 함수 (리팩토링된 버전)
*
* @param request - 요청 객체
* @returns 클라이언트 IP 주소
*/
export const getTrueClientIp = (request: RequestObject): string => {
const { headers, socket } = request
// IP 헤더 우선순위 목록
const ipHeaders = [
'True-Client-IP',
'X-Real-IP',
'X-Forwarded-For',
'Proxy-Client-IP',
'WL-Proxy-Client-IP',
'HTTP_CLIENT_IP',
'HTTP_X_FORWARDED_FOR',
]
// 헤더에서 IP 추출
for (const header of ipHeaders) {
const ip = headers[header] || headers[header.toLowerCase()]
if (ip?.trim()) {
// 여러 IP가 있는 경우 첫 번째 IP 반환
return ip.includes(',') ? ip.split(',')[0].trim() : ip.trim()
}
}
// 소켓에서 직접 IP 추출
if (socket.remoteAddress?.trim()) {
return socket.remoteAddress.trim()
}
return ''
}