diff --git a/src/components/auth/LoginForm.tsx b/src/components/auth/LoginForm.tsx index 6cacc0c..04f8e19 100644 --- a/src/components/auth/LoginForm.tsx +++ b/src/components/auth/LoginForm.tsx @@ -4,7 +4,7 @@ import { Link } from "react-router-dom"; import { Label } from "@/components/ui/label"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; -import { ArrowRight, Mail, KeyRound, Eye, EyeOff, AlertTriangle, Loader2, WifiOff, Wifi } from "lucide-react"; +import { ArrowRight, Mail, KeyRound, Eye, EyeOff, AlertTriangle, Loader2 } from "lucide-react"; interface LoginFormProps { email: string; @@ -17,8 +17,6 @@ interface LoginFormProps { isSettingUpTables?: boolean; loginError: string | null; handleLogin: (e: React.FormEvent) => Promise; - isOfflineMode?: boolean; - setIsOfflineMode?: (offline: boolean) => void; } const LoginForm: React.FC = ({ @@ -31,9 +29,7 @@ const LoginForm: React.FC = ({ isLoading, isSettingUpTables = false, loginError, - handleLogin, - isOfflineMode = false, - setIsOfflineMode + handleLogin }) => { // CORS 또는 JSON 관련 오류인지 확인 const isCorsOrJsonError = loginError && @@ -42,14 +38,6 @@ const LoginForm: React.FC = ({ loginError.includes('네트워크') || loginError.includes('404') || loginError.includes('Not Found')); - const toggleOfflineMode = () => { - if (setIsOfflineMode) { - const newMode = !isOfflineMode; - setIsOfflineMode(newMode); - localStorage.setItem('offline_mode', newMode.toString()); - } - }; - return (
@@ -91,27 +79,6 @@ const LoginForm: React.FC = ({
-
- -
- {loginError && (
@@ -119,18 +86,11 @@ const LoginForm: React.FC = ({

{loginError}

- {isCorsOrJsonError && !isOfflineMode && ( + {isCorsOrJsonError && (
  • 설정 페이지에서 다른 CORS 프록시 유형을 시도해 보세요.
  • HTTPS URL을 사용하는 Supabase 인스턴스로 변경해 보세요.
  • 네트워크 연결 상태를 확인하세요.
  • -
)}
@@ -138,21 +98,6 @@ const LoginForm: React.FC = ({
)} - {isOfflineMode && ( -
-
- -
-

오프라인 모드 활성화됨

-

서버 없이 앱의 기본 기능을 사용할 수 있습니다. 데이터는 로컬에 저장됩니다.

-

- 중요: 404 오류가 계속 발생한다면 오프라인 모드를 사용해보세요. -

-
-
-
- )} -
비밀번호를 잊으셨나요? @@ -166,7 +111,7 @@ const LoginForm: React.FC = ({ > {isLoading ? "로그인 중..." : isSettingUpTables ? "데이터베이스 설정 중..." : - isOfflineMode ? "오프라인 모드로 로그인" : "로그인"} + "로그인"} {!isLoading && !isSettingUpTables ? : } diff --git a/src/contexts/auth/signIn.ts b/src/contexts/auth/signIn.ts index cfee4d2..a5a33af 100644 --- a/src/contexts/auth/signIn.ts +++ b/src/contexts/auth/signIn.ts @@ -13,8 +13,8 @@ export const signIn = async (email: string, password: string) => { // 서버 연결 상태 먼저 확인 const connectionStatus = await verifyServerConnection(); if (!connectionStatus.connected) { - console.log('서버 연결 실패, 오프라인 모드 활성화를 고려하세요.'); - showAuthToast('서버 연결 실패', `${connectionStatus.message} (오프라인 모드를 사용해보세요)`, 'destructive'); + console.log('서버 연결 실패:', connectionStatus.message); + showAuthToast('서버 연결 실패', connectionStatus.message, 'destructive'); return { error: { message: `서버 연결에 실패했습니다: ${connectionStatus.message}` }, user: null @@ -79,7 +79,7 @@ export const signIn = async (email: string, password: string) => { // 네트워크 오류 확인 const errorMessage = handleNetworkError(error); - showAuthToast('로그인 오류', `${errorMessage} (오프라인 모드를 사용해보세요)`, 'destructive'); + showAuthToast('로그인 오류', errorMessage, 'destructive'); return { error: { message: errorMessage }, user: null }; } diff --git a/src/contexts/auth/signUp.ts b/src/contexts/auth/signUp.ts index 4a69704..1fd9bc9 100644 --- a/src/contexts/auth/signUp.ts +++ b/src/contexts/auth/signUp.ts @@ -1,109 +1,98 @@ import { supabase } from '@/lib/supabase'; -import { - handleNetworkError, - showAuthToast, - verifyServerConnection -} from '@/utils/auth'; +import { showAuthToast, verifyServerConnection } from '@/utils/auth'; import { signUpWithDirectApi } from './signUpUtils'; -export const signUp = async (email: string, password: string, username: string) => { +export const signUp = async (email: string, password: string) => { try { - console.log('회원가입 시도:', { email, username }); - // 서버 연결 상태 확인 const connectionStatus = await verifyServerConnection(); if (!connectionStatus.connected) { - console.warn('서버 연결 확인 실패:', connectionStatus.message); - showAuthToast('서버 연결 오류', `서버가 응답하지 않거나 오류 상태입니다: ${connectionStatus.message}. 오프라인 모드를 사용해보세요.`, 'destructive'); - return { error: { message: `서버 연결 오류: ${connectionStatus.message}` }, user: null }; + console.error('서버 연결 실패:', connectionStatus.message); + showAuthToast('회원가입 오류', `서버 연결 실패: ${connectionStatus.message}`, 'destructive'); + return { error: { message: connectionStatus.message }, user: null }; } - // 회원가입 시도 (기본 방식) + console.log('회원가입 시도:', email); + + // 기본 회원가입 시도 try { - console.log('Supabase URL:', supabase.auth.url); - // 온프레미스 서버에 적합한 옵션 추가 const { data, error } = await supabase.auth.signUp({ email, password, - options: { - data: { - username, - }, - // 이메일 확인 URL 생략 (온프레미스에서는 일반적으로 사용하지 않음) - emailRedirectTo: undefined - }, }); - + if (error) { console.error('회원가입 오류:', error); - // 서버 응답 오류 (404 등)인 경우 직접 API 호출 - if (error.message && ( + // REST API 오류인 경우 직접 API 호출 시도 + if (error.message.includes('json') || + error.message.includes('Unexpected end') || error.message.includes('404') || - error.message.includes('Not Found') || - error.message.includes('json') || - error.message.includes('Unexpected') - )) { - console.log('서버 응답 오류로 인해 직접 API 호출 시도'); - return await signUpWithDirectApi(email, password, username); + error.message.includes('Not Found')) { + console.warn('기본 회원가입 실패, 직접 API 호출 시도:', error.message); + return await signUpWithDirectApi(email, password); } - // 사용자 친화적인 오류 메시지 설정 + // 기타 오류 처리 let errorMessage = error.message; if (error.message.includes('User already registered')) { - errorMessage = '이미 등록된 이메일입니다. 로그인을 시도하세요.'; - } else if (error.message.includes('Password should be at least')) { - errorMessage = '비밀번호는 최소 6자 이상이어야 합니다.'; - } else if (error.message.includes('Invalid email')) { - errorMessage = '유효하지 않은 이메일 형식입니다.'; - } else if (error.message.includes('Unable to validate')) { - errorMessage = '서버 연결 문제: 이메일 검증에 실패했습니다.'; - } else if (error.message.includes('fetch failed')) { - errorMessage = '서버 연결에 실패했습니다. 네트워크 연결 또는 서버 상태를 확인하세요.'; - } else if (error.message.includes('Failed to fetch')) { - errorMessage = 'CORS 오류가 발생했습니다. 서버 설정을 확인하거나 네트워크 관리자에게 문의하세요.'; + errorMessage = '이미 등록된 사용자입니다.'; + } else if (error.message.includes('Signup not allowed')) { + errorMessage = '회원가입이 허용되지 않습니다.'; + } else if (error.message.includes('Email link invalid')) { + errorMessage = '이메일 링크가 유효하지 않습니다.'; } showAuthToast('회원가입 실패', errorMessage, 'destructive'); - return { error, user: null }; - } - - // 성공 메시지 표시 - showAuthToast('회원가입 성공', '이메일 확인 후 로그인해주세요.'); - - // 개발 환경 또는 데모 모드에서는 즉시 로그인 허용 - if (import.meta.env.DEV || process.env.NODE_ENV === 'development') { - // 개발 환경에서는 즉시 로그인 상태로 전환 - showAuthToast('개발 모드', '개발 환경에서는 이메일 확인 없이 로그인됩니다.'); + return { error: { message: errorMessage }, user: null }; } - return { error: null, user: data.user }; - } catch (signUpError: any) { - console.error('기본 회원가입 방식 실패:', signUpError); - - // 404 응답이나 API 오류 시 직접 API 호출 - if (signUpError.message && ( - signUpError.message.includes('json') || - signUpError.message.includes('Unexpected end') || - signUpError.message.includes('404') || - signUpError.message.includes('Not Found') - )) { - console.log('예외 발생으로 직접 API 호출 시도'); - return await signUpWithDirectApi(email, password, username); + // 회원가입 성공 + if (data && data.user) { + const isEmailConfirmationRequired = data.user.identities && + data.user.identities.length > 0 && + !data.user.identities[0].identity_data?.email_verified; + + if (isEmailConfirmationRequired) { + showAuthToast('회원가입 성공', '이메일 인증을 완료해주세요.', 'default'); + return { + error: null, + user: data.user, + message: '이메일 인증 필요', + emailConfirmationRequired: true + }; + } else { + showAuthToast('회원가입 성공', '환영합니다!', 'default'); + return { error: null, user: data.user }; + } } - // 그 외 오류는 그대로 던짐 - throw signUpError; + // 사용자 데이터가 없는 경우 (드물게 발생) + console.warn('회원가입 응답은 성공했지만 사용자 데이터가 없습니다'); + showAuthToast('회원가입 문제', '계정이 생성되었으나 응답에 사용자 정보가 없습니다.', 'destructive'); + return { error: { message: '회원가입 완료 처리 중 문제가 발생했습니다.' }, user: null }; + } catch (error: any) { + console.error('기본 회원가입 프로세스 예외:', error); + + // 직접 API 호출로 대체 시도 + if (error.message && ( + error.message.includes('json') || + error.message.includes('fetch') || + error.message.includes('404') || + error.message.includes('Not Found'))) { + console.warn('직접 API 호출로 재시도:', error); + return await signUpWithDirectApi(email, password); + } + + // 기타 예외 처리 + showAuthToast('회원가입 예외', error.message || '알 수 없는 오류', 'destructive'); + return { error: { message: error.message || '알 수 없는 오류' }, user: null }; } } catch (error: any) { - console.error('회원가입 중 예외 발생:', error); - - // 네트워크 오류 확인 - const errorMessage = handleNetworkError(error); - - showAuthToast('회원가입 오류', `${errorMessage} (오프라인 모드를 시도해보세요)`, 'destructive'); - return { error, user: null }; + console.error('회원가입 전역 예외:', error); + showAuthToast('회원가입 오류', error.message || '알 수 없는 오류', 'destructive'); + return { error: { message: error.message || '알 수 없는 오류' }, user: null }; } }; diff --git a/src/contexts/auth/signUpUtils.ts b/src/contexts/auth/signUpUtils.ts index 57e95fb..aa8ba7a 100644 --- a/src/contexts/auth/signUpUtils.ts +++ b/src/contexts/auth/signUpUtils.ts @@ -1,46 +1,37 @@ import { supabase } from '@/lib/supabase'; -import { parseResponse, showAuthToast, handleNetworkError } from '@/utils/auth'; +import { parseResponse, showAuthToast } from '@/utils/auth'; /** - * 직접 API 호출을 통한 회원가입 시도 (대체 방법) + * 직접 API 호출을 통한 회원가입 */ -export const signUpWithDirectApi = async (email: string, password: string, username: string) => { - console.log('직접 API 호출로 회원가입 시도'); - +export const signUpWithDirectApi = async (email: string, password: string) => { try { - // 요청 URL과 헤더 준비 - const signUpUrl = `${supabase.auth.url}/signup`; + console.log('직접 API 호출로 회원가입 시도 중'); + + const supabaseUrl = supabase.auth.url; + const supabaseKey = localStorage.getItem('supabase_key') || supabase.supabaseKey; + + // 회원가입 API 엔드포인트 및 헤더 설정 + const signUpUrl = `${supabaseUrl}/auth/v1/signup`; const headers = { 'Content-Type': 'application/json', - 'apikey': supabase.supabaseKey, - 'Authorization': `Bearer ${supabase.supabaseKey}`, + 'apikey': supabaseKey, + 'X-Client-Info': 'supabase-js/2.x' }; - console.log('회원가입 API 요청 URL:', signUpUrl); + console.log('회원가입 API 요청:', signUpUrl); - // 회원가입 요청 보내기 + // 회원가입 요청 전송 const response = await fetch(signUpUrl, { method: 'POST', headers, - body: JSON.stringify({ - email, - password, - data: { username } - }) + body: JSON.stringify({ email, password }) }); - console.log('회원가입 API 응답 상태:', response.status); + console.log('회원가입 응답 상태:', response.status); // HTTP 상태 코드 확인 - if (response.status === 401) { - showAuthToast('회원가입 실패', '인증 오류: API 키가 올바르지 않거나 서버 설정 문제가 있습니다.', 'destructive'); - return { - error: { message: '인증 오류: API 키가 올바르지 않거나 서버 설정 문제가 있습니다.' }, - user: null - }; - } - if (response.status === 404) { showAuthToast('회원가입 실패', '서버 경로를 찾을 수 없습니다. Supabase URL을 확인하세요.', 'destructive'); return { @@ -49,96 +40,65 @@ export const signUpWithDirectApi = async (email: string, password: string, usern }; } - if (response.status === 500) { - showAuthToast('회원가입 실패', '서버 내부 오류가 발생했습니다.', 'destructive'); - return { - error: { message: '서버 내부 오류가 발생했습니다.' }, - user: null - }; - } + // 응답 처리 + const responseData = await parseResponse(response); - // 응답 본문이 비어 있는지 확인 - const responseText = await response.text(); - if (!responseText || responseText.trim() === '') { - if (response.ok) { - // 응답이 비어 있지만 상태 코드가 성공인 경우 - showAuthToast('회원가입 가능성 있음', '서버가 빈 응답을 반환했지만 상태 코드는 성공입니다. 로그인을 시도해보세요.'); - return { - error: null, - user: { email, username } - }; - } else { - // 응답이 비어 있고 상태 코드가 실패인 경우 - showAuthToast('회원가입 실패', `서버가 빈 응답을 반환했습니다 (${response.status}). 서버 설정을 확인하세요.`, 'destructive'); - return { - error: { - message: `서버가 빈 응답을 반환했습니다 (${response.status}). 서버 설정을 확인하세요.` - }, - user: null - }; - } - } - - // 응답 JSON 파싱 시도 - let result; - try { - result = JSON.parse(responseText); - } catch (jsonError) { - console.error('JSON 파싱 오류:', jsonError, '원본 텍스트:', responseText); + if (responseData && responseData.error) { + const errorMessage = responseData.error_description || responseData.error || '회원가입 실패'; - if (response.ok) { - // 파싱 오류지만 상태 코드가 성공인 경우 - showAuthToast('회원가입 가능성 있음', '서버 응답을 해석할 수 없지만 상태 코드는 성공입니다. 로그인을 시도해보세요.'); + if (responseData.error === 'user_already_registered') { + showAuthToast('회원가입 실패', '이미 등록된 이메일 주소입니다.', 'destructive'); + return { error: { message: '이미 등록된 이메일 주소입니다.' }, user: null }; + } + + showAuthToast('회원가입 실패', errorMessage, 'destructive'); + return { error: { message: errorMessage }, user: null }; + } + + if (response.ok && responseData && responseData.id) { + const user = { + id: responseData.id, + email: responseData.email, + user_metadata: responseData.user_metadata || {}, + app_metadata: responseData.app_metadata || {}, + created_at: responseData.created_at, + }; + + const confirmEmail = !responseData.confirmed_at; + + if (confirmEmail) { + showAuthToast('회원가입 성공', '이메일 인증을 완료해주세요.', 'default'); return { error: null, - user: { email, username } + user, + message: '이메일 인증 필요', + emailConfirmationRequired: true }; } else { - // 파싱 오류이고 상태 코드도 실패인 경우 - showAuthToast('회원가입 실패', `서버 응답을 해석할 수 없습니다 (${response.status}). 서버 설정을 확인하세요.`, 'destructive'); - return { - error: { - message: `서버 응답을 해석할 수 없습니다 (${response.status}). 서버 설정을 확인하세요.` - }, - user: null - }; + showAuthToast('회원가입 성공', '환영합니다!', 'default'); + + // 성공 시 바로 로그인 세션 설정 시도 + try { + await supabase.auth.signInWithPassword({ email, password }); + } catch (loginError) { + console.warn('자동 로그인 실패:', loginError); + // 무시하고 계속 진행 (회원가입은 성공) + } + + return { error: null, user }; } + } else { + // 예상치 못한 응답 형식 + const errorMessage = '회원가입 처리 중 오류가 발생했습니다. 나중에 다시 시도하세요.'; + showAuthToast('회원가입 실패', errorMessage, 'destructive'); + return { error: { message: errorMessage }, user: null }; } - - // 응답 분석 및 결과 반환 - if (result.error) { - showAuthToast('회원가입 실패', result.error_description || result.error || '회원가입에 실패했습니다.', 'destructive'); - return { - error: { message: result.error_description || result.error }, - user: null - }; - } - - if (result.id || result.user || result.data?.user) { - showAuthToast('회원가입 성공', '이메일 확인 후 로그인해주세요.'); - return { - error: null, - user: result.user || result.data?.user || { id: result.id, email, username } - }; - } - - // 알 수 없는 응답 형식 - console.warn('알 수 없는 회원가입 API 응답 형식:', result); - showAuthToast('회원가입 결과 불명확', '로그인을 시도해보세요.', 'destructive'); - return { - error: { message: '서버 응답을 해석할 수 없습니다.' }, - user: null - }; } catch (error: any) { - console.error('직접 API 호출 회원가입 중 오류:', error); + console.error('회원가입 API 호출 중 예외 발생:', error); - // 네트워크 오류 확인 - const errorMessage = handleNetworkError(error); + const errorMessage = error.message || '알 수 없는 오류가 발생했습니다.'; + showAuthToast('회원가입 오류', errorMessage, 'destructive'); - showAuthToast('회원가입 API 호출 실패', errorMessage, 'destructive'); - return { - error: { message: errorMessage }, - user: null - }; + return { error: { message: errorMessage }, user: null }; } }; diff --git a/src/hooks/useLogin.ts b/src/hooks/useLogin.ts index 387e3f4..f44d5c1 100644 --- a/src/hooks/useLogin.ts +++ b/src/hooks/useLogin.ts @@ -4,12 +4,7 @@ import { useNavigate } from "react-router-dom"; import { useToast } from "@/hooks/useToast.wrapper"; import { useAuth } from "@/contexts/auth"; import { useTableSetup } from "@/hooks/useTableSetup"; -import { - getLoginErrorMessage, - showLoginErrorToast, - showLoginSuccessToast, - isCorsOrJsonError -} from "@/utils/auth/loginUtils"; +import { getLoginErrorMessage, showLoginErrorToast, showLoginSuccessToast } from "@/utils/auth/loginUtils"; export function useLogin() { const [email, setEmail] = useState(""); @@ -17,64 +12,12 @@ export function useLogin() { const [showPassword, setShowPassword] = useState(false); const [isLoading, setIsLoading] = useState(false); const [loginError, setLoginError] = useState(null); - // 네트워크 연결이 끊겼거나 로컬 스토리지에 오프라인 모드 설정이 있으면 기본값을 true로 설정 - const [isOfflineMode, setIsOfflineMode] = useState(() => - localStorage.getItem('offline_mode') === 'true' || !navigator.onLine - ); const navigate = useNavigate(); const { toast } = useToast(); const { signIn } = useAuth(); const { isSettingUpTables, setupTables } = useTableSetup(); - // 네트워크 상태 감지 - useEffect(() => { - const handleOnline = () => { - if (isOfflineMode && navigator.onLine) { - toast({ - title: "네트워크 연결됨", - description: "인터넷에 다시 연결되었습니다. 온라인 모드로 전환할 수 있습니다.", - }); - } - }; - - const handleOffline = () => { - if (!isOfflineMode) { - toast({ - title: "네트워크 연결 끊김", - description: "인터넷 연결이 끊겼습니다. 오프라인 모드로 전환합니다.", - variant: "destructive" - }); - // 자동으로 오프라인 모드로 전환 - setIsOfflineMode(true); - localStorage.setItem('offline_mode', 'true'); - } - }; - - window.addEventListener('online', handleOnline); - window.addEventListener('offline', handleOffline); - - return () => { - window.removeEventListener('online', handleOnline); - window.removeEventListener('offline', handleOffline); - }; - }, [isOfflineMode, toast]); - - // 로그인 시도 중 404 오류가 발생하면 자동으로 오프라인 모드 제안 - useEffect(() => { - if (loginError && ( - loginError.includes('404') || - loginError.includes('Not Found') || - loginError.includes('서버 연결') - )) { - toast({ - title: "서버 연결 문제", - description: "서버에 연결할 수 없습니다. 오프라인 모드를 사용해보세요.", - variant: "destructive" - }); - } - }, [loginError, toast]); - const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); setLoginError(null); @@ -91,12 +34,14 @@ export function useLogin() { setIsLoading(true); try { - // 오프라인 모드를 위한 환경 설정 - if (isOfflineMode) { - localStorage.setItem('offline_mode', 'true'); - // 오프라인 모드에서는 로컬 인증만 수행하고 바로 진행 - showLoginSuccessToast('오프라인 모드'); - navigate("/"); + // API 호출 전에 URL 유효성 확인 + const supabaseUrl = localStorage.getItem('supabase_url'); + if (!supabaseUrl || supabaseUrl.trim() === '') { + toast({ + title: "설정 오류", + description: "Supabase URL이 설정되지 않았습니다. 설정 페이지에서 구성하세요.", + variant: "destructive" + }); setIsLoading(false); return; } @@ -109,23 +54,18 @@ export function useLogin() { setLoginError(errorMessage); showLoginErrorToast(errorMessage); - // 404 오류인 경우 오프라인 모드 제안 + // 404 오류인 경우 경로 설정 확인 제안 if (errorMessage.includes('404') || errorMessage.includes('Not Found')) { toast({ - title: "오프라인 모드 제안", - description: "서버에 연결할 수 없습니다. 오프라인 모드를 사용해보세요.", - variant: "default" + title: "서버 연결 문제", + description: "Supabase URL 설정을 확인하세요. 서버 주소가 올바른지 확인하세요.", + variant: "destructive" }); } } else if (user) { // 로그인 성공 showLoginSuccessToast(); - - // 온라인 모드에서만 테이블 설정 - if (!isOfflineMode) { - await setupTables(); - } - + await setupTables(); navigate("/"); } else { // user가 없지만 error도 없는 경우 (드문 경우) @@ -147,17 +87,6 @@ export function useLogin() { description: errorMessage, variant: "destructive" }); - - // 네트워크 관련 오류인 경우 오프라인 모드 제안 - if (errorMessage.includes('network') || - errorMessage.includes('fetch') || - errorMessage.includes('서버')) { - toast({ - title: "오프라인 모드 제안", - description: "서버 연결에 문제가 있습니다. 오프라인 모드를 사용해보세요.", - variant: "default" - }); - } } finally { setIsLoading(false); } @@ -174,9 +103,6 @@ export function useLogin() { isSettingUpTables, loginError, setLoginError, - isOfflineMode, - setIsOfflineMode, - handleLogin, - isCorsOrJsonError: (msg: string | null) => isCorsOrJsonError(msg) + handleLogin }; } diff --git a/src/lib/supabase/client.ts b/src/lib/supabase/client.ts index 0493a20..658cef1 100644 --- a/src/lib/supabase/client.ts +++ b/src/lib/supabase/client.ts @@ -52,9 +52,11 @@ try { // Storage API 엔드포인트 경로 수정 (buckets → bucket) if (url.includes('/storage/v1/buckets')) { - url = url.replace('/storage/v1/buckets', '/storage/v1/bucket'); + const newUrl = url.replace('/storage/v1/buckets', '/storage/v1/bucket'); // Request 객체인 경우 새 Request 객체 생성 - requestToUse = new Request(url, requestToUse); + const newRequest = new Request(newUrl, requestToUse); + requestToUse = newRequest; + url = newUrl; console.log('Storage API Request 객체 경로 수정:', url); } } @@ -113,7 +115,7 @@ try { try { // 기본 서버 상태 확인 (CORS 테스트) console.log('Supabase 서버 상태 확인 중...'); - const response = await fetch(`${supabaseUrl}/rest/v1/`, { + const response = await fetch(`${supabaseUrl}/auth/v1/`, { method: 'GET', headers: { 'Content-Type': 'application/json', @@ -121,14 +123,14 @@ try { }, }); - if (response.ok) { - console.log('Supabase REST API 연결 성공:', response.status); + if (response.ok || response.status === 401 || response.status === 404) { + console.log('Supabase 서버 연결 성공:', response.status); } else { - console.warn('Supabase REST API 연결 실패:', response.status, response.statusText); + console.warn('Supabase 서버 연결 실패:', response.status, response.statusText); // 응답 세부 정보 로깅 try { const errorText = await response.text(); - console.warn('Supabase REST API 오류 응답:', errorText); + console.warn('Supabase 서버 응답 내용:', errorText); } catch (e) { console.error('응답 내용 읽기 실패:', e); } diff --git a/src/pages/Register.tsx b/src/pages/Register.tsx index 8c77d32..7f10cff 100644 --- a/src/pages/Register.tsx +++ b/src/pages/Register.tsx @@ -3,7 +3,7 @@ import React, { useState, useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useAuth } from "@/contexts/auth"; import { useToast } from "@/hooks/useToast.wrapper"; -import { verifyServerConnection } from "@/contexts/auth/auth.utils"; +import { verifyServerConnection, verifySupabaseConnection } from "@/utils/auth/networkUtils"; // 분리된 컴포넌트들 임포트 import RegisterHeader from "@/components/auth/RegisterHeader"; @@ -46,19 +46,32 @@ const Register = () => { // 서버 연결 상태 확인 함수 const checkServerConnection = async () => { try { - const status = await verifyServerConnection(); - setServerStatus({ - checked: true, - connected: status.connected, - message: status.message - }); + // 먼저 기본 연결 확인 + const basicStatus = await verifyServerConnection(); - // 연결 실패 시 토스트 표시 - if (!status.connected) { - toast({ - title: "서버 연결 문제", - description: status.message, - variant: "destructive" + // 기본 확인에 실패하면 강화된 확인 시도 + if (!basicStatus.connected) { + const enhancedStatus = await verifySupabaseConnection(); + + setServerStatus({ + checked: true, + connected: enhancedStatus.connected, + message: enhancedStatus.message + (enhancedStatus.details ? ` (${enhancedStatus.details})` : '') + }); + + if (!enhancedStatus.connected) { + toast({ + title: "서버 연결 문제", + description: enhancedStatus.message, + variant: "destructive" + }); + } + } else { + // 기본 확인에 성공한 경우 + setServerStatus({ + checked: true, + connected: true, + message: basicStatus.message }); } } catch (error: any) { @@ -67,6 +80,12 @@ const Register = () => { connected: false, message: `연결 확인 중 오류: ${error.message || '알 수 없는 오류'}` }); + + toast({ + title: "연결 확인 오류", + description: error.message || '알 수 없는 오류', + variant: "destructive" + }); } }; diff --git a/src/utils/auth/networkUtils.ts b/src/utils/auth/networkUtils.ts index bf15350..9cc50ee 100644 --- a/src/utils/auth/networkUtils.ts +++ b/src/utils/auth/networkUtils.ts @@ -16,35 +16,6 @@ export const hasCorsIssue = (error: any): boolean => { ); }; -/** - * Supabase 연결 상태 확인 - */ -export const checkSupabaseConnection = async (): Promise => { - try { - const { data, error } = await supabase.auth.getSession(); - - // 오류가 없으면 연결 성공 - if (!error) { - return true; - } - - // 오류 메시지 확인 - if (error.message && ( - error.message.includes('Failed to fetch') || - error.message.includes('Network') || - error.message.includes('CORS') - )) { - return false; - } - - // 다른 오류는 연결은 됐지만 세션/인증 관련 오류이므로 연결은 성공 - return true; - } catch (err) { - console.error('Supabase 연결 확인 중 오류:', err); - return false; - } -}; - /** * 서버 연결 상태 검사 */ @@ -59,15 +30,22 @@ export const verifyServerConnection = async (): Promise<{ // Supabase URL 가져오기 const supabaseUrl = supabase.auth.url; + if (!supabaseUrl) { + return { + connected: false, + message: 'Supabase URL이 설정되지 않았습니다. 설정 페이지에서 구성하세요.' + }; + } + // 단순 헬스 체크 요청 try { - const response = await fetch(`${supabaseUrl}/`, { + const response = await fetch(`${supabaseUrl}/auth/v1/`, { method: 'GET', headers: { - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', + 'apikey': localStorage.getItem('supabase_key') || '' }, - // 연결 확인만을 위한 요청이므로 짧은 타임아웃 설정 - signal: AbortSignal.timeout(3000) + signal: AbortSignal.timeout(5000) // 타임아웃 시간 증가 }); const elapsed = Date.now() - start; @@ -87,28 +65,27 @@ export const verifyServerConnection = async (): Promise<{ }; } } catch (fetchError: any) { - console.error('기본 연결 확인 실패, 대체 경로 시도:', fetchError); - - // 대체 경로로 재시도 + console.error('기본 연결 확인 실패, 상태 확인 시도:', fetchError); + try { - const altResponse = await fetch(`${supabaseUrl}/auth/v1/`, { + // 대체 경로로 상태 확인 + const altResponse = await fetch(`${supabaseUrl}/`, { method: 'GET', headers: { 'Content-Type': 'application/json' }, - signal: AbortSignal.timeout(3000) + signal: AbortSignal.timeout(5000) }); // 어떤 응답이라도 오면 서버가 살아있다고 간주 const elapsed = Date.now() - start; return { connected: true, - message: `서버 연결 성공 (대체 경로, 응답 시간: ${elapsed}ms)`, + message: `서버 연결 성공 (기본 경로, 응답 시간: ${elapsed}ms)`, statusCode: altResponse.status }; } catch (altError) { - // 두 가지 경로 모두 실패 - console.error('대체 경로 확인도 실패:', altError); + console.error('기본 경로 확인도 실패:', altError); throw fetchError; // 원래 에러를 던짐 } } @@ -140,3 +117,62 @@ export const verifyServerConnection = async (): Promise<{ }; } }; + +/** + * 강화된 서버 연결 검사: 다양한 경로로 시도 + */ +export const verifySupabaseConnection = async (): Promise<{ + connected: boolean; + message: string; + statusCode?: number; + details?: string; +}> => { + const supabaseUrl = localStorage.getItem('supabase_url'); + if (!supabaseUrl) { + return { + connected: false, + message: 'Supabase URL이 설정되지 않았습니다' + }; + } + + // 무작위 쿼리 파라미터를 추가하여 캐시 방지 + const cacheParam = `?_nocache=${Date.now()}`; + + // 다양한 경로를 순차적으로 시도 + const paths = [ + '/auth/v1/', + '/', + '/rest/v1/', + '/storage/v1/' + ]; + + for (const path of paths) { + try { + const response = await fetch(`${supabaseUrl}${path}${cacheParam}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + signal: AbortSignal.timeout(5000) + }); + + // 어떤 응답이든 서버가 살아있다는 신호로 간주 + return { + connected: true, + message: `서버 연결 성공 (${path})`, + statusCode: response.status, + details: `${response.status} ${response.statusText}` + }; + } catch (error) { + console.warn(`${path} 경로 연결 실패:`, error); + // 계속 다음 경로 시도 + } + } + + // 모든 경로 시도 실패 + return { + connected: false, + message: '모든 Supabase 경로에 대한 연결 시도 실패', + details: '네트워크 연결 또는 서버 주소를 확인하세요' + }; +};