|
@@ -14,20 +14,30 @@ export function clearApiToken() {
|
|
|
export function apiRequest(method, url, payload = null, {
|
|
|
expectJson = true,
|
|
|
useForm = false,
|
|
|
- timeout = 10000,
|
|
|
- requireAuth = true // 👈 기본은 true, 로그인 등은 false
|
|
|
+ requireAuth = true, // 인증이 필요한지 여부
|
|
|
+ timeout = 10000
|
|
|
} = {}) {
|
|
|
const token = getApiToken();
|
|
|
if (requireAuth && !token) {
|
|
|
- return Promise.reject("토큰 없음");
|
|
|
+ return Promise.reject({ status: 401, message: "토큰 없음" });
|
|
|
}
|
|
|
|
|
|
+ const normalizedMethod = method.toUpperCase();
|
|
|
const headers = {};
|
|
|
- // 인증이 필요한 경우에만 Authorization 헤더 추가
|
|
|
+
|
|
|
+ // 인증 헤더 조건부 추가
|
|
|
if (requireAuth && token) {
|
|
|
headers["Authorization"] = `Bearer ${token}`;
|
|
|
}
|
|
|
|
|
|
+ // 컨텐츠 타입 지정
|
|
|
+ if (useForm) {
|
|
|
+ headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
|
+ } else if (normalizedMethod !== "GET" && normalizedMethod !== "DELETE") {
|
|
|
+ headers["Content-Type"] = "application/json";
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
// CSRF 토큰 자동 추가
|
|
|
const csrfToken = document.querySelector("meta[name='_csrf']")?.content;
|
|
|
const csrfHeader = document.querySelector("meta[name='_csrf_header']")?.content;
|
|
@@ -35,7 +45,6 @@ export function apiRequest(method, url, payload = null, {
|
|
|
headers[csrfHeader] = csrfToken;
|
|
|
}
|
|
|
|
|
|
- const normalizedMethod = method.toUpperCase();
|
|
|
let fullUrl = url;
|
|
|
let body = null;
|
|
|
|
|
@@ -46,13 +55,9 @@ export function apiRequest(method, url, payload = null, {
|
|
|
}
|
|
|
// POST/PUT = JSON 또는 x-www-form-urlencoded
|
|
|
else if (payload) {
|
|
|
- if (useForm) {
|
|
|
- headers["Content-Type"] = "application/x-www-form-urlencoded";
|
|
|
- body = new URLSearchParams(payload).toString();
|
|
|
- } else {
|
|
|
- headers["Content-Type"] = "application/json";
|
|
|
- body = JSON.stringify(payload);
|
|
|
- }
|
|
|
+ body = useForm
|
|
|
+ ? new URLSearchParams(payload).toString()
|
|
|
+ : JSON.stringify(payload);
|
|
|
}
|
|
|
|
|
|
// 타임아웃 처리
|
|
@@ -64,8 +69,8 @@ export function apiRequest(method, url, payload = null, {
|
|
|
headers,
|
|
|
body,
|
|
|
signal: controller.signal
|
|
|
- }).then(res =>
|
|
|
- res.text().then(raw => {
|
|
|
+ })
|
|
|
+ .then(res => res.text().then(raw => {
|
|
|
clearTimeout(timeoutId);
|
|
|
if (!res.ok) {
|
|
|
return Promise.reject({
|
|
@@ -80,16 +85,52 @@ export function apiRequest(method, url, payload = null, {
|
|
|
} catch {
|
|
|
return raw;
|
|
|
}
|
|
|
- })
|
|
|
- ).catch(err => {
|
|
|
- clearTimeout(timeoutId);
|
|
|
- if (err.name === "AbortError") {
|
|
|
- return Promise.reject({ status: 408, message: "요청 시간이 초과되었습니다." });
|
|
|
- }
|
|
|
- return Promise.reject({
|
|
|
- status: err.status || 500,
|
|
|
- message: err.message || "알 수 없는 오류",
|
|
|
- body: err.body || null
|
|
|
+ }))
|
|
|
+ .catch(err => {
|
|
|
+ clearTimeout(timeoutId);
|
|
|
+
|
|
|
+ if (err.name === "AbortError") {
|
|
|
+ return Promise.reject({
|
|
|
+ status: 408,
|
|
|
+ message: "요청 시간이 초과되었습니다." });
|
|
|
+ }
|
|
|
+
|
|
|
+ return Promise.reject({
|
|
|
+ status: err?.status || 500,
|
|
|
+ message: err?.message || "알 수 없는 오류",
|
|
|
+ body: err?.body || null
|
|
|
+ });
|
|
|
});
|
|
|
- });
|
|
|
+}
|
|
|
+
|
|
|
+export function handleApiError(error) {
|
|
|
+ const { status, message, body } = error;
|
|
|
+
|
|
|
+ if (error instanceof TypeError) {
|
|
|
+ alert("네트워크 연결 오류: 인터넷 상태를 확인해주세요.");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (status) {
|
|
|
+ case 401:
|
|
|
+ alert("로그인이 필요합니다.");
|
|
|
+ location.href = "/login";
|
|
|
+ break;
|
|
|
+ case 403:
|
|
|
+ alert("접근 권한이 없습니다.");
|
|
|
+ break;
|
|
|
+ case 404:
|
|
|
+ alert("요청하신 정보를 찾을 수 없습니다.");
|
|
|
+ break;
|
|
|
+ case 408:
|
|
|
+ alert("요청 시간이 초과되었습니다. 다시 시도해주세요.");
|
|
|
+ break;
|
|
|
+ case 500:
|
|
|
+ alert("서버 오류 발생. 관리자에게 문의해주세요.");
|
|
|
+ console.error("서버 응답:", body);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ alert(`요청 실패: ${message || "알 수 없는 오류입니다."}`);
|
|
|
+ console.error("에러 상세:", error);
|
|
|
+ }
|
|
|
}
|