File name
Commit message
Commit date
File name
Commit message
Commit date
File name
Commit message
Commit date
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ?? '';
const ADMIN_LOGIN_URL = '/uat/uia/actionSecurityLogin.do';
const ADMIN_ID = 'admin';
const ADMIN_PASSWORD = '1';
const MAX_RETRY_COUNT = 3;
type ApiResponse<T> = {
success: boolean;
message: string;
data: T;
}
class ApiClient {
private loginPromise: Promise<void> | null = null;
private toApiUrl(path: string) {
if (/^https?:\/\//i.test(path)) {
return path;
}
return `${API_BASE_URL}${path}`;
}
private createHeaders(headers?: HeadersInit) {
const result = new Headers(headers);
result.set('X-Requested-With', 'XMLHttpRequest');
return result;
}
private async login() {
if (!this.loginPromise) {
this.loginPromise = fetch(this.toApiUrl(ADMIN_LOGIN_URL), {
method: 'POST',
credentials: 'include',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-Requested-With': 'XMLHttpRequest',
},
body: new URLSearchParams({
id: ADMIN_ID,
password: ADMIN_PASSWORD,
}),
}).then(async (response) => {
if (!response.ok) {
throw new Error(`로그인 실패 : ${response.status}`);
}
const contentType = response.headers.get('content-type') ?? '';
if (contentType === 'application/json') {
const result = (await response.json()) as ApiResponse<unknown>;
if (!result.success) {
throw new Error(result.message);
}
}
}).finally(() => {
this.loginPromise = null;
});
}
return this.loginPromise;
}
private async needsLogin(response: Response) {
if(response.status === 401 || response.status === 403) {
return true;
}
const contentType= response.headers.get("content-type") ?? '';
if (contentType.includes('text/html')) {
const html = await response.clone().text();
return (
html.includes('/uat/uia/actionMain.do') ||
html.includes('actionSecurityLogin.do')
);
}
if (contentType.includes('application/json')) {
try {
const result = (await response.clone().json()) as ApiResponse<unknown>;
return (!result.success && /login|로그인/i.test(result.message ?? ''));
} catch {
return false;
}
}
return false;
}
private async request(
path: string,
init: RequestInit = {},
retryCount = 0
): Promise<Response> {
const response = await fetch(this.toApiUrl(path), {
...init,
credentials: 'include',
headers: this.createHeaders(init.headers),
redirect: 'manual',
});
if (await this.needsLogin(response)) {
if (retryCount >= MAX_RETRY_COUNT) {
throw new Error('인증 재시도 횟수 초과');
}
await this.login();
return this.request(path, init, retryCount + 1);
}
return response;
}
async get<T>(path: string): Promise<T> {
const response = await this.request(path, {
method: 'GET',
});
return this.parseJson<T>(response);
}
async post<T>(
path: string,
body?: unknown
): Promise<T> {
const response = await this.request(path, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined,
headers: {
'Content-Type': 'application/json',
},
});
return this.parseJson<T>(response);
}
private async parseJson<T>(response: Response): Promise<T> {
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
const result = (await response.json()) as ApiResponse<T>;
if (!result.success) {
throw new Error(result.message ?? 'API request failed');
}
return result.data as T;
}
}
export const apiClient = new ApiClient();