diff --git a/src/components/supabase/SupabaseConnectionTest.tsx b/src/components/supabase/SupabaseConnectionTest.tsx index c63d0b1..09dd13c 100644 --- a/src/components/supabase/SupabaseConnectionTest.tsx +++ b/src/components/supabase/SupabaseConnectionTest.tsx @@ -1,9 +1,10 @@ import React, { useState } from 'react'; import { Button } from "@/components/ui/button"; -import { RefreshCw } from "lucide-react"; +import { RefreshCw, Info } from "lucide-react"; import { toast } from "@/hooks/useToast.wrapper"; import { testSupabaseConnection } from '@/lib/supabase'; +import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible"; interface TestResult { url: string; @@ -14,11 +15,13 @@ interface TestResult { auth: boolean; database: boolean; errors: string[]; + debugInfo?: any; } const SupabaseConnectionTest: React.FC = () => { const [testResults, setTestResults] = useState(null); const [isTesting, setIsTesting] = useState(false); + const [showDebug, setShowDebug] = useState(false); const runConnectionTest = async () => { setIsTesting(true); @@ -107,6 +110,59 @@ const SupabaseConnectionTest: React.FC = () => { ))} )} + + {/* 디버그 정보 섹션 추가 */} + {testResults.debugInfo && ( + +
+

고급 진단 정보

+ + + +
+ + +
+
+ Supabase URL: +
+ {testResults.url} +
+
+ +
+ 클라이언트 초기화: +
+ {testResults.client ? '성공' : '실패'} +
+
+ +
+ 브라우저 정보: +
+ {testResults.debugInfo.browserInfo} +
+
+ +
+ 테스트 시간: +
+ {new Date(testResults.debugInfo.timestamp).toLocaleString()} +
+
+
+
+
+ )} )} diff --git a/src/components/supabase/SupabaseHelpSection.tsx b/src/components/supabase/SupabaseHelpSection.tsx index fc50581..f65b63d 100644 --- a/src/components/supabase/SupabaseHelpSection.tsx +++ b/src/components/supabase/SupabaseHelpSection.tsx @@ -1,5 +1,6 @@ import React from 'react'; +import { AlertCircle } from 'lucide-react'; const SupabaseHelpSection: React.FC = () => { return ( @@ -17,12 +18,35 @@ const SupabaseHelpSection: React.FC = () => { Anon Key: Supabase 대시보드의 API 설정에서 찾을 수 있는 anon/public 키를 입력합니다.
  • - CORS 프록시: HTTP URL에 대한 브라우저 보안 제한을 우회하기 위해 사용합니다. HTTPS가 아닌 URL에 접근 문제가 있는 경우 활성화하세요. + CORS 프록시: HTTP URL에 대한 브라우저 보안 제한을 우회하기 위해 사용합니다. + HTTPS가 아닌 URL에 접근 문제가 있는 경우 활성화하세요.
  • 가능하면 HTTPS URL을 사용하는 것이 좋습니다. HTTP URL을 사용해야 하는 경우 CORS 프록시를 활성화하세요.
  • + + {/* 오류 해결 팁 추가 */} +
    +

    + + 연결 문제 해결 팁 +

    + +
    ); }; diff --git a/src/lib/supabase/config.ts b/src/lib/supabase/config.ts index 1041a6b..e9fff1c 100644 --- a/src/lib/supabase/config.ts +++ b/src/lib/supabase/config.ts @@ -9,7 +9,14 @@ export const getSupabaseUrl = () => { if (useProxy) { // CORS 프록시 URL로 변환 (URL 구조 개선) const cleanUrl = storedUrl.trim(); - return `https://corsproxy.io/?${encodeURIComponent(cleanUrl)}`; + // URL에 이미 프로토콜이 포함되어 있는지 확인 + const hasProtocol = cleanUrl.startsWith('http://') || cleanUrl.startsWith('https://'); + const urlForProxy = hasProtocol ? cleanUrl : `http://${cleanUrl}`; + + // 프록시 URL 생성 시 더 정확한 인코딩 + const proxyUrl = `https://corsproxy.io/?${encodeURIComponent(urlForProxy)}`; + console.log('CORS 프록시 URL 생성:', proxyUrl); + return proxyUrl; } return storedUrl; } @@ -43,8 +50,13 @@ export const configureSupabase = (url: string, key: string, useProxy: boolean = // URL 정리 (앞뒤 공백 제거) const cleanUrl = url.trim(); + // URL에 프로토콜이 없는 경우 http:// 추가 + const normalizedUrl = (cleanUrl.startsWith('http://') || cleanUrl.startsWith('https://')) + ? cleanUrl + : `http://${cleanUrl}`; + // 로컬 스토리지에 설정 저장 - localStorage.setItem('supabase_url', cleanUrl); + localStorage.setItem('supabase_url', normalizedUrl); localStorage.setItem('supabase_key', key); localStorage.setItem('use_cors_proxy', useProxy.toString()); diff --git a/src/lib/supabase/tests.ts b/src/lib/supabase/tests.ts index c9cc6aa..018a71a 100644 --- a/src/lib/supabase/tests.ts +++ b/src/lib/supabase/tests.ts @@ -27,43 +27,130 @@ export const testSupabaseLogin = async (email: string, password: string) => { // API 테스트 도우미 함수 export const testSupabaseConnection = async () => { + const originalUrl = getOriginalSupabaseUrl(); + const proxyUrl = getSupabaseUrl(); + const usingProxy = isCorsProxyEnabled(); + const supabaseKey = getSupabaseKey(); + const results = { - url: getOriginalSupabaseUrl(), // 원본 URL - proxyUrl: getSupabaseUrl(), // 프록시 적용된 URL - usingProxy: isCorsProxyEnabled(), // 프록시 사용 여부 + url: originalUrl, // 원본 URL + proxyUrl: proxyUrl, // 프록시 적용된 URL + usingProxy: usingProxy, // 프록시 사용 여부 client: !!supabase, restApi: false, auth: false, database: false, - errors: [] as string[] + errors: [] as string[], + debugInfo: { + originalUrl, + proxyUrl, + usingProxy, + keyLength: supabaseKey.length, + browserInfo: navigator.userAgent, + timestamp: new Date().toISOString() + } }; + console.log('연결 테스트 시작 - 설정 정보:', { + originalUrl, + proxyUrl, + usingProxy + }); + try { - // 1. REST API 접근 테스트 + // 1. REST API 접근 테스트 - 다양한 URL 형식 시도 try { console.log('REST API 테스트 시작...'); - // 정확한 REST API 엔드포인트 구성 - const apiUrl = results.proxyUrl.endsWith('/') - ? `${results.proxyUrl}rest/v1/` - : `${results.proxyUrl}/rest/v1/`; - console.log('REST API 테스트 URL:', apiUrl); + // 여러 형태의 REST API 엔드포인트 URL 구성 시도 + let apiUrl = ''; + let response = null; + let errorBody = ''; - const response = await fetch(apiUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - 'apikey': getSupabaseKey(), - }, - }); + // 1번째 시도: 기본 URL + /rest/v1/ + apiUrl = proxyUrl.endsWith('/') + ? `${proxyUrl}rest/v1/` + : `${proxyUrl}/rest/v1/`; - results.restApi = response.ok; - if (!response.ok) { - const errorBody = await response.text(); - results.errors.push(`REST API 오류(${response.status} ${response.statusText}): ${errorBody || '응답 없음'}`); - console.error('REST API 테스트 실패:', response.status, errorBody); - } else { - console.log('REST API 테스트 성공'); + console.log('REST API 테스트 URL (시도 1):', apiUrl); + + try { + response = await fetch(apiUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'apikey': supabaseKey, + }, + }); + + if (response.ok) { + results.restApi = true; + console.log('REST API 테스트 성공 (시도 1)'); + } else { + errorBody = await response.text(); + console.warn(`REST API 테스트 실패 (시도 1) - 상태: ${response.status}, 오류: ${errorBody}`); + + // 2번째 시도: corsproxy.io URL 직접 구성 + if (usingProxy) { + const directProxyUrl = `https://corsproxy.io/?${encodeURIComponent(`${originalUrl}/rest/v1/`)}`; + console.log('REST API 테스트 URL (시도 2 - 직접 프록시):', directProxyUrl); + + try { + response = await fetch(directProxyUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'apikey': supabaseKey, + }, + }); + + if (response.ok) { + results.restApi = true; + console.log('REST API 테스트 성공 (시도 2)'); + } else { + const error2 = await response.text(); + console.warn(`REST API 테스트 실패 (시도 2) - 상태: ${response.status}, 오류: ${error2}`); + results.errors.push(`REST API 오류(${response.status}): ${error2 || errorBody || '응답 없음'}`); + } + } catch (innerErr: any) { + console.error('REST API 테스트 시도 2 예외:', innerErr); + results.errors.push(`REST API 예외 (시도 2): ${innerErr.message || '알 수 없는 오류'}`); + } + } else { + results.errors.push(`REST API 오류(${response.status}): ${errorBody || '응답 없음'}`); + } + } + } catch (outerErr: any) { + console.error('REST API 테스트 시도 1 예외:', outerErr); + results.errors.push(`REST API 예외 (시도 1): ${outerErr.message || '알 수 없는 오류'}`); + + // 백업 시도 + if (usingProxy) { + try { + // 백업 프록시 시도: 다른 CORS 프록시 서비스 사용 + const backupProxyUrl = `https://api.allorigins.win/raw?url=${encodeURIComponent(`${originalUrl}/rest/v1/`)}`; + console.log('REST API 테스트 URL (백업 프록시):', backupProxyUrl); + + response = await fetch(backupProxyUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'apikey': supabaseKey, + }, + }); + + if (response.ok) { + results.restApi = true; + console.log('REST API 테스트 성공 (백업 프록시)'); + results.debugInfo.backupProxySuccess = true; + } else { + const backupError = await response.text(); + console.warn(`REST API 테스트 실패 (백업 프록시) - 상태: ${response.status}, 오류: ${backupError}`); + } + } catch (backupErr: any) { + console.error('REST API 백업 프록시 예외:', backupErr); + } + } } } catch (err: any) { results.errors.push(`REST API 예외: ${err.message || '알 수 없는 오류'}`);