검증게시판 단계 추가

This commit is contained in:
Macbook
2026-05-28 16:24:14 +09:00
parent f64dc1e983
commit 7e3644cb62
375 changed files with 4539 additions and 251294 deletions
+63 -13
View File
@@ -26,6 +26,11 @@ export function setApiBase(url: string): void {
storageSetSync('gc_api_base_url', API_BASE);
}
/** initStorage() 이후 Preferences에 저장된 API 주소를 반영 */
export function refreshApiBaseFromStorage(): void {
API_BASE = resolveApiBase();
}
/** STOMP WebSocket 연결에 사용할 HTTP base URL (api 경로 제거) */
export function getApiBase(): string {
return API_BASE.replace(/\/api$/, '');
@@ -77,12 +82,56 @@ function authHeaders(): Record<string, string> {
return headers;
}
const DEFAULT_FETCH_TIMEOUT_MS = 30_000;
const LOGIN_FETCH_TIMEOUT_MS = 60_000;
function wrapNetworkError(e: unknown): Error {
if (e instanceof TypeError || (e instanceof Error && /failed to fetch/i.test(e.message))) {
return new Error(
`서버에 연결할 수 없습니다 (${API_BASE}). Wi‑Fi·API 주소·앱 재설치 후 설정의 API URL을 확인하세요.`,
);
}
return e instanceof Error ? e : new Error(String(e));
}
async function fetchWithTimeout(
url: string,
init: RequestInit | undefined,
timeoutMs: number,
): Promise<Response> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
return await fetch(url, {
...init,
signal: controller.signal,
headers: { ...authHeaders(), ...(init?.headers ?? {}) },
});
} catch (e) {
if (e instanceof DOMException && e.name === 'AbortError') {
throw new Error(
`서버 응답 시간 초과 (${Math.round(timeoutMs / 1000)}초). ${API_BASE} · WiFi를 확인하세요.`,
);
}
throw wrapNetworkError(e);
} finally {
clearTimeout(timer);
}
}
/** 로그인 API 오류 시 메시지 throw */
async function requestOrThrow<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${API_BASE}${path}`, {
...init,
headers: { ...authHeaders(), ...(init?.headers ?? {}) },
});
async function requestOrThrow<T>(
path: string,
init?: RequestInit,
timeoutMs: number = DEFAULT_FETCH_TIMEOUT_MS,
): Promise<T> {
let res: Response;
try {
res = await fetchWithTimeout(`${API_BASE}${path}`, init, timeoutMs);
} catch (e) {
if (e instanceof Error) throw e;
throw wrapNetworkError(e);
}
const text = await res.text().catch(() => '');
if (!res.ok) {
let msg = `요청 실패 (${res.status})`;
@@ -99,10 +148,7 @@ async function requestOrThrow<T>(path: string, init?: RequestInit): Promise<T> {
async function request<T>(path: string, init?: RequestInit): Promise<T | null> {
try {
const res = await fetch(`${API_BASE}${path}`, {
...init,
headers: { ...authHeaders(), ...(init?.headers ?? {}) },
});
const res = await fetchWithTimeout(`${API_BASE}${path}`, init, DEFAULT_FETCH_TIMEOUT_MS);
if (res.status === 204) return null;
if (!res.ok) {
console.error(`[backendApi] ${init?.method ?? 'GET'} ${path}${res.status}`);
@@ -814,10 +860,14 @@ export async function saveRolePermissions(
}
export async function loginUser(username: string, password: string): Promise<LoginResponse> {
return requestOrThrow<LoginResponse>('/auth/login', {
method: 'POST',
body: JSON.stringify({ username, password }),
});
return requestOrThrow<LoginResponse>(
'/auth/login',
{
method: 'POST',
body: JSON.stringify({ username, password }),
},
LOGIN_FETCH_TIMEOUT_MS,
);
}
export async function fetchAuthMe(): Promise<LoginResponse | null> {