diff --git a/src/utils/auth/index.ts b/src/utils/auth/index.ts index 8cf9e9e..f4f84d4 100644 --- a/src/utils/auth/index.ts +++ b/src/utils/auth/index.ts @@ -5,3 +5,6 @@ export * from './networkUtils'; export * from './responseUtils'; export * from './validationUtils'; export * from './handleNetworkError'; + +// 새로운 네트워크 모듈도 직접 내보냅니다 (선택적) +export * from './network'; diff --git a/src/utils/auth/network/basicConnectionCheck.ts b/src/utils/auth/network/basicConnectionCheck.ts new file mode 100644 index 0000000..a34dd34 --- /dev/null +++ b/src/utils/auth/network/basicConnectionCheck.ts @@ -0,0 +1,138 @@ + +import { getSupabaseUrl, isCorsProxyEnabled, getProxyType, getOriginalSupabaseUrl } from '@/lib/supabase/config'; +import { logProxyInfo, handleHttpUrlWithoutProxy } from './corsUtils'; + +/** + * 서버 연결 상태 검사 + */ +export const verifyServerConnection = async (): Promise<{ + connected: boolean; + message: string; + statusCode?: number; +}> => { + try { + const start = Date.now(); + + // Supabase URL 가져오기 (프록시 적용 URL) + const supabaseUrl = getSupabaseUrl(); + + if (!supabaseUrl) { + return { + connected: false, + message: 'Supabase URL이 설정되지 않았습니다. 설정 페이지에서 구성하세요.' + }; + } + + // 프록시 설정 상태 확인 + logProxyInfo(); + + // 단순 헬스 체크 요청 - 무작위 쿼리 파라미터 추가 + const cacheParam = `?_nocache=${Date.now()}`; + + try { + const response = await fetch(`${supabaseUrl}/auth/v1/${cacheParam}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'apikey': localStorage.getItem('supabase_key') || '' + }, + signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 + }); + + const elapsed = Date.now() - start; + + // 200, 401, 404 응답도 서버가 살아있다는 신호로 간주 + if (response.ok || response.status === 401 || response.status === 404) { + return { + connected: true, + message: `서버 연결 성공 (응답 시간: ${elapsed}ms)`, + statusCode: response.status + }; + } else { + return { + connected: false, + message: `서버 응답 오류: ${response.status} ${response.statusText}`, + statusCode: response.status + }; + } + } catch (fetchError: any) { + console.error('기본 연결 확인 실패, 상태 확인 시도:', fetchError); + + // HTTP URL을 사용하는데 프록시가 비활성화된 경우 처리 + if (handleHttpUrlWithoutProxy()) { + return { + connected: false, + message: 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.' + }; + } + + try { + // 대체 경로로 상태 확인 - 무작위 쿼리 파라미터 추가 + const altResponse = await fetch(`${supabaseUrl}/${cacheParam}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json' + }, + signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 + }); + + // 어떤 응답이라도 오면 서버가 살아있다고 간주 + const elapsed = Date.now() - start; + return { + connected: true, + message: `서버 연결 성공 (기본 경로, 응답 시간: ${elapsed}ms)`, + statusCode: altResponse.status + }; + } catch (altError) { + console.error('기본 경로 확인도 실패:', altError); + throw fetchError; // 원래 에러를 던짐 + } + } + } catch (error: any) { + console.error('서버 연결 확인 중 오류:', error); + + // 프록시 설정 확인 + const usingProxy = isCorsProxyEnabled(); + const proxyType = getProxyType(); + + // 오류 유형에 따른 메시지 설정 + let errorMessage = '알 수 없는 네트워크 오류'; + + if (error.message) { + if (error.message.includes('Failed to fetch')) { + errorMessage = 'CORS 정책 오류 또는 서버 연결 실패'; + } else if (error.message.includes('NetworkError')) { + errorMessage = '네트워크 연결 실패'; + } else if (error.message.includes('TypeError')) { + errorMessage = '네트워크 요청 형식 오류'; + } else if (error.message.includes('timeout') || error.message.includes('timed out')) { + errorMessage = '서버 응답 시간 초과'; + } else if (error.message.includes('aborted')) { + errorMessage = '요청이 중단됨'; + } else { + errorMessage = error.message; + } + } + + // HTTP URL을 사용하는데 프록시가 비활성화된 경우 처리 + if (handleHttpUrlWithoutProxy()) { + errorMessage = 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.'; + } + + // Cloudflare 프록시 추천 메시지 추가 + if (errorMessage.includes('CORS') || errorMessage.includes('fetch') || errorMessage.includes('네트워크')) { + if (!usingProxy) { + console.log('CORS 오류 감지, Cloudflare 프록시 사용 권장'); + errorMessage += '. Cloudflare CORS 프록시 사용을 권장합니다.'; + } else if (proxyType !== 'cloudflare') { + console.log('CORS 오류 감지, Cloudflare 프록시로 변경 권장'); + errorMessage += '. Cloudflare CORS 프록시로 변경을 권장합니다.'; + } + } + + return { + connected: false, + message: errorMessage + }; + } +}; diff --git a/src/utils/auth/network/corsUtils.ts b/src/utils/auth/network/corsUtils.ts new file mode 100644 index 0000000..3c7c02d --- /dev/null +++ b/src/utils/auth/network/corsUtils.ts @@ -0,0 +1,45 @@ + +import { isCorsProxyEnabled, getProxyType, getSupabaseUrl, getOriginalSupabaseUrl } from '@/lib/supabase/config'; + +/** + * CORS 문제 확인 + */ +export const hasCorsIssue = (error: any): boolean => { + if (!error) return false; + + const errorMessage = error.message || ''; + return ( + errorMessage.includes('Failed to fetch') || + errorMessage.includes('CORS') || + errorMessage.includes('Network') || + errorMessage.includes('프록시') + ); +}; + +/** + * HTTP URL이 프록시 없이 사용되고 있는지 확인하고 처리 + */ +export const handleHttpUrlWithoutProxy = (): boolean => { + // HTTP URL을 사용하는데 프록시가 비활성화된 경우 + const originalUrl = getOriginalSupabaseUrl(); + const usingProxy = isCorsProxyEnabled(); + + if (originalUrl.startsWith('http:') && !usingProxy) { + // 자동으로 프록시 활성화 + localStorage.setItem('use_cors_proxy', 'true'); + localStorage.setItem('proxy_type', 'cloudflare'); + return true; + } + return false; +}; + +/** + * 프록시 정보 로깅 + */ +export const logProxyInfo = (): void => { + const usingProxy = isCorsProxyEnabled(); + const proxyType = getProxyType(); + const supabaseUrl = getSupabaseUrl(); + + console.log(`연결 테스트 - CORS 프록시: ${usingProxy ? '사용 중' : '미사용'}, 타입: ${proxyType}, URL: ${supabaseUrl}`); +}; diff --git a/src/utils/auth/network/enhancedConnectionCheck.ts b/src/utils/auth/network/enhancedConnectionCheck.ts new file mode 100644 index 0000000..a24fbbf --- /dev/null +++ b/src/utils/auth/network/enhancedConnectionCheck.ts @@ -0,0 +1,80 @@ + +import { getSupabaseUrl, isCorsProxyEnabled, getProxyType, getOriginalSupabaseUrl } from '@/lib/supabase/config'; +import { logProxyInfo, handleHttpUrlWithoutProxy } from './corsUtils'; + +/** + * 강화된 서버 연결 검사: 다양한 경로로 시도 + */ +export const verifySupabaseConnection = async (): Promise<{ + connected: boolean; + message: string; + statusCode?: number; + details?: string; +}> => { + const supabaseUrl = getSupabaseUrl(); + if (!supabaseUrl) { + return { + connected: false, + message: 'Supabase URL이 설정되지 않았습니다' + }; + } + + // 프록시 정보 로깅 + const usingProxy = isCorsProxyEnabled(); + const proxyType = getProxyType(); + console.log(`강화된 연결 테스트 - 프록시: ${usingProxy ? '사용 중' : '미사용'}, 타입: ${proxyType}, URL: ${supabaseUrl}`); + + // 무작위 쿼리 파라미터를 추가하여 캐시 방지 + const cacheParam = `?_nocache=${Date.now()}`; + + // 다양한 경로를 순차적으로 시도 + const paths = [ + '/auth/v1/', + '/', + '/rest/v1/', + '/storage/v1/' + ]; + + for (const path of paths) { + try { + console.log(`경로 시도: ${path}`); + const response = await fetch(`${supabaseUrl}${path}${cacheParam}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'apikey': localStorage.getItem('supabase_key') || '' + }, + signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 + }); + + console.log(`경로 ${path} 응답 상태:`, response.status); + + // 어떤 응답이든 서버가 살아있다는 신호로 간주 + return { + connected: true, + message: `서버 연결 성공 (${path})`, + statusCode: response.status, + details: `${response.status} ${response.statusText}` + }; + } catch (error) { + console.warn(`${path} 경로 연결 실패:`, error); + // 계속 다음 경로 시도 + } + } + + // HTTP URL을 사용하는데 프록시가 비활성화된 경우 처리 + if (handleHttpUrlWithoutProxy()) { + return { + connected: false, + message: 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.', + details: 'CORS 제한으로 인해 HTTP URL에 직접 접근할 수 없습니다' + }; + } + + // 모든 경로 시도 실패 + return { + connected: false, + message: '모든 Supabase 경로에 대한 연결 시도 실패', + details: '네트워크 연결 또는 서버 주소를 확인하세요' + }; +}; diff --git a/src/utils/auth/network/index.ts b/src/utils/auth/network/index.ts new file mode 100644 index 0000000..3f33c5b --- /dev/null +++ b/src/utils/auth/network/index.ts @@ -0,0 +1,5 @@ + +// 네트워크 유틸리티 모듈 +export { hasCorsIssue, handleHttpUrlWithoutProxy, logProxyInfo } from './corsUtils'; +export { verifyServerConnection } from './basicConnectionCheck'; +export { verifySupabaseConnection } from './enhancedConnectionCheck'; diff --git a/src/utils/auth/networkUtils.ts b/src/utils/auth/networkUtils.ts index 7d65ab9..ab5c965 100644 --- a/src/utils/auth/networkUtils.ts +++ b/src/utils/auth/networkUtils.ts @@ -1,247 +1,8 @@ -import { supabase } from '@/lib/supabase'; -import { getProxyType, isCorsProxyEnabled, getSupabaseUrl, getOriginalSupabaseUrl } from '@/lib/supabase/config'; - -/** - * CORS 문제 확인 - */ -export const hasCorsIssue = (error: any): boolean => { - if (!error) return false; - - const errorMessage = error.message || ''; - return ( - errorMessage.includes('Failed to fetch') || - errorMessage.includes('CORS') || - errorMessage.includes('Network') || - errorMessage.includes('프록시') - ); -}; - -/** - * 서버 연결 상태 검사 - */ -export const verifyServerConnection = async (): Promise<{ - connected: boolean; - message: string; - statusCode?: number; -}> => { - try { - const start = Date.now(); - - // Supabase URL 가져오기 (프록시 적용 URL) - const supabaseUrl = getSupabaseUrl(); - - if (!supabaseUrl) { - return { - connected: false, - message: 'Supabase URL이 설정되지 않았습니다. 설정 페이지에서 구성하세요.' - }; - } - - // 프록시 설정 상태 확인 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - console.log(`연결 테스트 - CORS 프록시: ${usingProxy ? '사용 중' : '미사용'}, 타입: ${proxyType}, URL: ${supabaseUrl}`); - - // 단순 헬스 체크 요청 - 무작위 쿼리 파라미터 추가 - const cacheParam = `?_nocache=${Date.now()}`; - - try { - const response = await fetch(`${supabaseUrl}/auth/v1/${cacheParam}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'apikey': localStorage.getItem('supabase_key') || '' - }, - signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 - }); - - const elapsed = Date.now() - start; - - // 200, 401, 404 응답도 서버가 살아있다는 신호로 간주 - if (response.ok || response.status === 401 || response.status === 404) { - return { - connected: true, - message: `서버 연결 성공 (응답 시간: ${elapsed}ms)`, - statusCode: response.status - }; - } else { - return { - connected: false, - message: `서버 응답 오류: ${response.status} ${response.statusText}`, - statusCode: response.status - }; - } - } catch (fetchError: any) { - console.error('기본 연결 확인 실패, 상태 확인 시도:', fetchError); - - // HTTP URL을 사용하는데 프록시가 비활성화된 경우 - const originalUrl = getOriginalSupabaseUrl(); - if (originalUrl.startsWith('http:') && !usingProxy) { - // 자동으로 프록시 활성화 - localStorage.setItem('use_cors_proxy', 'true'); - localStorage.setItem('proxy_type', 'cloudflare'); - - return { - connected: false, - message: 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.' - }; - } - - try { - // 대체 경로로 상태 확인 - 무작위 쿼리 파라미터 추가 - const altResponse = await fetch(`${supabaseUrl}/${cacheParam}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json' - }, - signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 - }); - - // 어떤 응답이라도 오면 서버가 살아있다고 간주 - const elapsed = Date.now() - start; - return { - connected: true, - message: `서버 연결 성공 (기본 경로, 응답 시간: ${elapsed}ms)`, - statusCode: altResponse.status - }; - } catch (altError) { - console.error('기본 경로 확인도 실패:', altError); - throw fetchError; // 원래 에러를 던짐 - } - } - } catch (error: any) { - console.error('서버 연결 확인 중 오류:', error); - - // 프록시 설정 확인 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - - // 오류 유형에 따른 메시지 설정 - let errorMessage = '알 수 없는 네트워크 오류'; - - if (error.message) { - if (error.message.includes('Failed to fetch')) { - errorMessage = 'CORS 정책 오류 또는 서버 연결 실패'; - } else if (error.message.includes('NetworkError')) { - errorMessage = '네트워크 연결 실패'; - } else if (error.message.includes('TypeError')) { - errorMessage = '네트워크 요청 형식 오류'; - } else if (error.message.includes('timeout') || error.message.includes('timed out')) { - errorMessage = '서버 응답 시간 초과'; - } else if (error.message.includes('aborted')) { - errorMessage = '요청이 중단됨'; - } else { - errorMessage = error.message; - } - } - - // HTTP URL을 사용하는데 프록시가 비활성화된 경우 - const originalUrl = getOriginalSupabaseUrl(); - if (originalUrl.startsWith('http:') && !usingProxy) { - // 자동으로 프록시 활성화 - localStorage.setItem('use_cors_proxy', 'true'); - localStorage.setItem('proxy_type', 'cloudflare'); - - errorMessage = 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.'; - } - - // Cloudflare 프록시 추천 메시지 추가 - if (errorMessage.includes('CORS') || errorMessage.includes('fetch') || errorMessage.includes('네트워크')) { - if (!usingProxy) { - console.log('CORS 오류 감지, Cloudflare 프록시 사용 권장'); - errorMessage += '. Cloudflare CORS 프록시 사용을 권장합니다.'; - } else if (proxyType !== 'cloudflare') { - console.log('CORS 오류 감지, Cloudflare 프록시로 변경 권장'); - errorMessage += '. Cloudflare CORS 프록시로 변경을 권장합니다.'; - } - } - - return { - connected: false, - message: errorMessage - }; - } -}; - -/** - * 강화된 서버 연결 검사: 다양한 경로로 시도 - */ -export const verifySupabaseConnection = async (): Promise<{ - connected: boolean; - message: string; - statusCode?: number; - details?: string; -}> => { - const supabaseUrl = getSupabaseUrl(); - if (!supabaseUrl) { - return { - connected: false, - message: 'Supabase URL이 설정되지 않았습니다' - }; - } - - // 프록시 정보 로깅 - const usingProxy = isCorsProxyEnabled(); - const proxyType = getProxyType(); - console.log(`강화된 연결 테스트 - 프록시: ${usingProxy ? '사용 중' : '미사용'}, 타입: ${proxyType}, URL: ${supabaseUrl}`); - - // 무작위 쿼리 파라미터를 추가하여 캐시 방지 - const cacheParam = `?_nocache=${Date.now()}`; - - // 다양한 경로를 순차적으로 시도 - const paths = [ - '/auth/v1/', - '/', - '/rest/v1/', - '/storage/v1/' - ]; - - for (const path of paths) { - try { - console.log(`경로 시도: ${path}`); - const response = await fetch(`${supabaseUrl}${path}${cacheParam}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'apikey': localStorage.getItem('supabase_key') || '' - }, - signal: AbortSignal.timeout(15000) // 타임아웃 시간 증가 - }); - - console.log(`경로 ${path} 응답 상태:`, response.status); - - // 어떤 응답이든 서버가 살아있다는 신호로 간주 - return { - connected: true, - message: `서버 연결 성공 (${path})`, - statusCode: response.status, - details: `${response.status} ${response.statusText}` - }; - } catch (error) { - console.warn(`${path} 경로 연결 실패:`, error); - // 계속 다음 경로 시도 - } - } - - // HTTP URL을 사용하는데 프록시가 비활성화된 경우 - const originalUrl = getOriginalSupabaseUrl(); - if (originalUrl.startsWith('http:') && !usingProxy) { - // 자동으로 프록시 활성화 - localStorage.setItem('use_cors_proxy', 'true'); - localStorage.setItem('proxy_type', 'cloudflare'); - - return { - connected: false, - message: 'HTTP URL에 직접 접근할 수 없어 CORS 프록시를 자동으로 활성화했습니다. 페이지를 새로고침하고 다시 시도하세요.', - details: 'CORS 제한으로 인해 HTTP URL에 직접 접근할 수 없습니다' - }; - } - - // 모든 경로 시도 실패 - return { - connected: false, - message: '모든 Supabase 경로에 대한 연결 시도 실패', - details: '네트워크 연결 또는 서버 주소를 확인하세요' - }; -}; +// 이 파일은 이전 버전과의 호환성을 위해 유지됩니다. +// 새 코드에서는 utils/auth/network 모듈을 직접 사용하세요. +export { + hasCorsIssue, + verifyServerConnection, + verifySupabaseConnection +} from './network';