diff --git a/src/screens/auth/LoginScreen.tsx b/src/screens/auth/LoginScreen.tsx index ce569f5..0d9c7e6 100644 --- a/src/screens/auth/LoginScreen.tsx +++ b/src/screens/auth/LoginScreen.tsx @@ -24,7 +24,7 @@ export default function LoginScreen({ navigation }) { const [loading, setLoading] = useState(false); // Direct Spotify App Client ID - const SPOTIFY_CLIENT_ID = "7fa1e7acbf7e44f18bf28d74f14fb9cb"; + const SPOTIFY_CLIENT_ID = process.env.EXPO_PUBLIC_SPOTIFY_CLIENT_ID || "7fa1e7acbf7e44f18bf28d74f14fb9cb"; // Configure Dynamic Redirect URI const redirectUri = makeRedirectUri({ @@ -42,6 +42,13 @@ export default function LoginScreen({ navigation }) { discovery ); + useEffect(() => { + console.log("[SpotifyAuthDebug] Platform:", Platform.OS); + console.log("[SpotifyAuthDebug] redirectUri:", redirectUri); + console.log("[SpotifyAuthDebug] Client ID exists in env:", Boolean(process.env.EXPO_PUBLIC_SPOTIFY_CLIENT_ID)); + console.log("[SpotifyAuthDebug] Scopes being requested:", ['user-read-email', 'user-read-private', 'playlist-modify-public', 'playlist-modify-private']); + }, []); + const showSpotifyLoginFailure = () => { Alert.alert('Erro de Autenticação', 'Não foi possível iniciar sessão com Spotify. Tenta novamente ou usa login normal.'); }; @@ -49,9 +56,29 @@ export default function LoginScreen({ navigation }) { // Handle Spotify OAuth Response useEffect(() => { if (response) { - console.log("SPOTIFY_AUTH_RESULT:", response); + // Redact tokens/codes from logs to protect secrets + const res = response as any; + const cleanResponse = { + ...res, + authentication: res.authentication ? { ...res.authentication, accessToken: '[REDACTED]', refreshToken: '[REDACTED]' } : undefined, + params: res.params ? { + ...res.params, + access_token: res.params.access_token ? '[REDACTED]' : undefined, + refresh_token: res.params.refresh_token ? '[REDACTED]' : undefined, + code: res.params.code ? '[REDACTED_CODE]' : undefined, + } : undefined + }; + console.log("[SpotifyAuthDebug] 2. Full AuthSession response object (sanitized):", JSON.stringify(cleanResponse, null, 2)); + console.log("[SpotifyAuthDebug] 3. response.type:", response.type); + console.log("[SpotifyAuthDebug] 4. response.error:", res.error || "none"); + console.log("[SpotifyAuthDebug] 5. response.params.error:", res.params?.error || "none"); + console.log("[SpotifyAuthDebug] 5. response.params.error_description:", res.params?.error_description || "none"); + console.log("[SpotifyAuthDebug] 6. Received authorization code:", Boolean(res.params?.code)); + console.log("[SpotifyAuthDebug] 6. Received access token:", Boolean(res.params?.access_token || res.authentication?.accessToken)); + if (response.type === 'success') { - const { code } = response.params; + const { code } = res.params; + console.log("[SpotifyAuthDebug] Stage: After redirecting back - Success received."); console.log("[LoginScreen] Spotify auth success:", { redirectUri, clientIdExists: Boolean(SPOTIFY_CLIENT_ID), @@ -61,15 +88,19 @@ export default function LoginScreen({ navigation }) { flow: 'authorization_code_pkce', }); exchangeCodeForTokens(code); - } else if (response.type === 'error') { - console.warn('[LoginScreen] Spotify OAuth response error:', response.error?.message || response.error); - Alert.alert('Erro de Autenticação', response.error?.message || 'Falha ao logar com o Spotify'); + } else { + console.warn("[SpotifyAuthDebug] Stage: After redirecting back - Auth session ended without success status. Type:", response.type); + if (response.type === 'error') { + console.warn('[LoginScreen] Spotify OAuth response error:', res.error?.message || res.error); + Alert.alert('Erro de Autenticação', res.error?.message || 'Falha ao logar com o Spotify'); + } } } }, [response]); const exchangeCodeForTokens = async (code: string) => { if (!request?.codeVerifier) { + console.warn('[SpotifyAuthDebug] Stage: Token Exchange Failed (Missing PKCE code verifier)'); console.warn('[LoginScreen] Missing Spotify PKCE code verifier:', { redirectUri, clientIdExists: Boolean(SPOTIFY_CLIENT_ID), @@ -82,6 +113,7 @@ export default function LoginScreen({ navigation }) { try { setLoading(true); + console.log('[SpotifyAuthDebug] Stage: Performing Token Exchange'); console.log('[LoginScreen] Spotify token exchange start:', { redirectUri, clientIdExists: Boolean(SPOTIFY_CLIENT_ID), @@ -114,6 +146,7 @@ export default function LoginScreen({ navigation }) { }); if (!tokenResponse.ok) { + console.warn('[SpotifyAuthDebug] Stage: Token Exchange Failed (HTTP Error)'); console.warn('[LoginScreen] Spotify token exchange failed:', { httpStatus: tokenResponse.status, responseBody: tokenResponseText, @@ -126,6 +159,7 @@ export default function LoginScreen({ navigation }) { try { tokenResult = JSON.parse(tokenResponseText); } catch (parseError) { + console.warn('[SpotifyAuthDebug] Stage: Token Exchange Failed (JSON Parse Error)'); console.warn('[LoginScreen] Spotify token response JSON parse failed:', parseError); showSpotifyLoginFailure(); return; @@ -139,6 +173,7 @@ export default function LoginScreen({ navigation }) { await setSpotifyRefreshToken(tokenResult.refresh_token); } + console.log("[SpotifyAuthDebug] Stage: Fetching Spotify Profile"); console.log("SPOTIFY_PROFILE_FETCH_START"); const profileRes = await fetch('https://api.spotify.com/v1/me', { headers: { Authorization: `Bearer ${tokenResult.access_token}` } @@ -152,6 +187,7 @@ export default function LoginScreen({ navigation }) { }); if (!profileRes.ok) { + console.warn('[SpotifyAuthDebug] Stage: Fetching Spotify Profile Failed (HTTP Error)'); console.warn('[LoginScreen] Spotify profile fetch failed:', { httpStatus: profileRes.status, responseBody: profileText, @@ -164,11 +200,13 @@ export default function LoginScreen({ navigation }) { try { profile = JSON.parse(profileText); } catch (parseError) { + console.warn('[SpotifyAuthDebug] Stage: Fetching Spotify Profile Failed (JSON Parse Error)'); console.warn('[LoginScreen] Spotify profile JSON parse failed:', parseError); showSpotifyLoginFailure(); return; } + console.log("[SpotifyAuthDebug] Stage: Completing Supabase Auth and updating metadata"); console.log("SPOTIFY_PROFILE_NAME:", profile.display_name); console.log("SPOTIFY_PROFILE_EMAIL_EXISTS:", !!profile.email); console.log("SPOTIFY_USER_ID:", profile.id); @@ -277,6 +315,8 @@ export default function LoginScreen({ navigation }) { const handleSpotifyLogin = async () => { try { + console.log("[SpotifyAuthDebug] 1. Exact redirectUri at login press:", redirectUri); + console.log("[SpotifyAuthDebug] Stage: Before Spotify redirects back (Launching Spotify browser...)"); console.log("[LoginScreen] Spotify login start:", { redirectUri, clientIdExists: Boolean(SPOTIFY_CLIENT_ID), @@ -285,7 +325,9 @@ export default function LoginScreen({ navigation }) { flow: 'authorization_code_pkce', }); await promptAsync(); + console.log("[SpotifyAuthDebug] Browser promptAsync promise resolved."); } catch (e: any) { + console.warn('[SpotifyAuthDebug] Failure before Spotify redirects back (Error during promptAsync):', e?.message || e); console.warn('🚀 [LoginScreen] OAuth Error:', e?.message || e); showSpotifyLoginFailure(); } diff --git a/src/screens/auth/RegisterScreen.tsx b/src/screens/auth/RegisterScreen.tsx index 93f8b0c..4987a45 100644 --- a/src/screens/auth/RegisterScreen.tsx +++ b/src/screens/auth/RegisterScreen.tsx @@ -26,7 +26,7 @@ export default function RegisterScreen({ navigation }) { const [loading, setLoading] = useState(false); // Direct Spotify App Client ID - const SPOTIFY_CLIENT_ID = "7fa1e7acbf7e44f18bf28d74f14fb9cb"; + const SPOTIFY_CLIENT_ID = process.env.EXPO_PUBLIC_SPOTIFY_CLIENT_ID || "7fa1e7acbf7e44f18bf28d74f14fb9cb"; // Configure Dynamic Redirect URI const redirectUri = makeRedirectUri({ diff --git a/src/screens/trip/NewTripScreen.tsx b/src/screens/trip/NewTripScreen.tsx index 63f508d..7b54458 100644 --- a/src/screens/trip/NewTripScreen.tsx +++ b/src/screens/trip/NewTripScreen.tsx @@ -80,7 +80,7 @@ export default function NewTripScreen({ navigation }) { if (providerToken) { // Proactively check if token is valid, or refresh it - console.log("Validating Spotify token..."); + console.log("Validating Spotify token via GET /v1/me..."); let testRes = await fetch('https://api.spotify.com/v1/me', { headers: { Authorization: `Bearer ${providerToken}` } }); @@ -90,19 +90,17 @@ export default function NewTripScreen({ navigation }) { const newToken = await refreshSpotifyToken(); if (newToken) { providerToken = newToken; + console.log("Spotify token refreshed successfully!"); } else { console.log("Failed to refresh Spotify token."); providerToken = null; } } else if (!testRes.ok) { const testErr = await testRes.text(); - if (testRes.status === 403 || testErr.toLowerCase().includes('active premium subscription required')) { - spotifyPremiumRequired = true; - console.warn("Spotify validation skipped due to expected Premium limitation:", testRes.status, testErr); - } else { - console.warn("Spotify validation request failed:", testRes.status, testErr); - } + console.warn("Spotify validation request failed on GET /v1/me:", testRes.status, testErr); providerToken = null; + } else { + console.log("Spotify token is valid (GET /v1/me returned 200 OK)."); } } @@ -154,6 +152,10 @@ export default function NewTripScreen({ navigation }) { } // D. Create empty playlist + console.log("[SpotifyPlaylistDebug] playlist create request started"); + console.log("[SpotifyPlaylistDebug] userId exists:", Boolean(spotifyUserId)); + console.log("[SpotifyPlaylistDebug] access token exists:", Boolean(providerToken)); + const createPlaylistRes = await fetch(`https://api.spotify.com/v1/users/${spotifyUserId}/playlists`, { method: 'POST', headers: { @@ -166,7 +168,21 @@ export default function NewTripScreen({ navigation }) { public: false }) }); - const playlistData = await safeParseJson(createPlaylistRes, 'CreatePlaylist'); + + console.log("[SpotifyPlaylistDebug] create playlist HTTP status:", createPlaylistRes.status); + const createPlaylistResText = await createPlaylistRes.text(); + if (!createPlaylistRes.ok) { + console.log("[SpotifyPlaylistDebug] create playlist response body if failed:", createPlaylistResText); + throw new Error(`Spotify API returned status ${createPlaylistRes.status} for CreatePlaylist: ${createPlaylistResText.substring(0, 150)}`); + } + + let playlistData: any; + try { + playlistData = JSON.parse(createPlaylistResText); + } catch (e) { + throw new Error(`Failed to parse JSON response for CreatePlaylist: ${createPlaylistResText.substring(0, 150)}`); + } + if (!playlistData.id) throw new Error('Could not create playlist'); const playlistId = playlistData.id; generatedPlaylistUrl = playlistData.external_urls.spotify; @@ -225,6 +241,8 @@ export default function NewTripScreen({ navigation }) { const chunkSize = 100; for (let i = 0; i < trackUris.length; i += chunkSize) { const chunk = trackUris.slice(i, i + chunkSize); + + console.log("[SpotifyPlaylistDebug] Add tracks request started for chunk:", i / chunkSize + 1); const addTracksRes = await fetch(`https://api.spotify.com/v1/playlists/${playlistId}/tracks`, { method: 'POST', headers: { @@ -233,8 +251,11 @@ export default function NewTripScreen({ navigation }) { }, body: JSON.stringify({ uris: chunk }) }); + + console.log("[SpotifyPlaylistDebug] add tracks HTTP status:", addTracksRes.status); if (!addTracksRes.ok) { const addTracksErr = await addTracksRes.text(); + console.log("[SpotifyPlaylistDebug] add tracks response body if failed:", addTracksErr); if (addTracksRes.status === 403 || addTracksErr.toLowerCase().includes('active premium subscription required')) { spotifyPremiumRequired = true; console.warn("Spotify add tracks skipped due to expected Premium limitation:", addTracksRes.status, addTracksErr); diff --git a/src/services/destinationImage.ts b/src/services/destinationImage.ts index 139b760..f041e1c 100644 --- a/src/services/destinationImage.ts +++ b/src/services/destinationImage.ts @@ -97,6 +97,7 @@ const getGooglePlacesImage = async (normalizedDestination: string, debugContext? destination: normalizedDestination, apiKeyExists: Boolean(GOOGLE_MAPS_API_KEY), }); + console.log("[GooglePlacesDebug] EXPO_PUBLIC_GOOGLE_MAPS_API_KEY loaded exists:", Boolean(GOOGLE_MAPS_API_KEY)); if (!GOOGLE_MAPS_API_KEY) { console.warn('[DestinationImage] Google Places skipped: missing API key'); @@ -121,6 +122,9 @@ const getGooglePlacesImage = async (normalizedDestination: string, debugContext? const response = await fetch(url); const data = (await response.json()) as PlacesSearchResponse; + console.log("[GooglePlacesDebug] Google API Status:", data.status); + console.log("[GooglePlacesDebug] Google API Error Message:", data.error_message || "none"); + console.log('[DestinationImage] Google Places result:', { tripTitle: debugContext?.tripTitle || null, destination: normalizedDestination, @@ -190,57 +194,95 @@ const getWikimediaImage = async (normalizedDestination: string, debugContext?: D const languageHosts = ['pt.wikipedia.org', 'en.wikipedia.org']; for (const host of languageHosts) { - const searchUrl = `https://${host}/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(normalizedDestination)}&format=json&origin=*&srlimit=1`; - console.log('[DestinationImage] Wikimedia search:', { - tripTitle: debugContext?.tripTitle || null, - destination: normalizedDestination, - host, - url: searchUrl, - }); + try { + const searchUrl = `https://${host}/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(normalizedDestination)}&format=json&origin=*&srlimit=1`; + console.log('[DestinationImage] Wikimedia search:', { + tripTitle: debugContext?.tripTitle || null, + destination: normalizedDestination, + host, + url: searchUrl, + }); - const searchResponse = await fetch(searchUrl); - const searchData = (await searchResponse.json()) as WikimediaSearchResponse; - const title = searchData.query?.search?.[0]?.title; + const searchResponse = await fetch(searchUrl); + if (!searchResponse.ok) { + console.warn(`[DestinationImage] Wikimedia search request failed for host ${host} with status:`, searchResponse.status); + continue; + } - console.log('[DestinationImage] Wikimedia search result:', { - tripTitle: debugContext?.tripTitle || null, - destination: normalizedDestination, - host, - httpStatus: searchResponse.status, - title: title || null, - body: searchData, - }); + const searchContentType = searchResponse.headers.get("content-type") || ""; + if (!searchContentType.includes("application/json")) { + const text = await searchResponse.text(); + console.warn(`[DestinationImage] Wikimedia search returned non-JSON for host ${host}:`, text.substring(0, 100)); + continue; + } - if (!searchResponse.ok || !title) { - continue; + const searchData = (await searchResponse.json()) as WikimediaSearchResponse; + const title = searchData.query?.search?.[0]?.title; + + console.log('[DestinationImage] Wikimedia search result:', { + tripTitle: debugContext?.tripTitle || null, + destination: normalizedDestination, + host, + httpStatus: searchResponse.status, + title: title || null, + }); + + if (!title) { + continue; + } + + // Wikipedia REST API requires spaces to be replaced by underscores in titles + const formattedTitle = title.replace(/ /g, '_'); + const summaryUrl = `https://${host}/api/rest_v1/page/summary/${encodeURIComponent(formattedTitle)}`; + + console.log('[DestinationImage] Wikimedia summary search:', { + tripTitle: debugContext?.tripTitle || null, + destination: normalizedDestination, + host, + title, + url: summaryUrl, + }); + + const summaryResponse = await fetch(summaryUrl); + if (!summaryResponse.ok) { + console.warn(`[DestinationImage] Wikimedia summary request failed for ${title} with status:`, summaryResponse.status); + continue; + } + + const summaryContentType = summaryResponse.headers.get("content-type") || ""; + if (!summaryContentType.includes("application/json")) { + const text = await summaryResponse.text(); + console.warn(`[DestinationImage] Wikimedia summary returned non-JSON for ${title}:`, text.substring(0, 100)); + continue; + } + + const summaryData = (await summaryResponse.json()) as WikimediaPageSummary; + const imageUrl = summaryData.originalimage?.source || summaryData.thumbnail?.source || null; + + console.log('[DestinationImage] Wikimedia image result:', { + tripTitle: debugContext?.tripTitle || null, + destination: normalizedDestination, + host, + title: summaryData.title || title, + httpStatus: summaryResponse.status, + imageUrl, + fallbackReason: imageUrl ? null : 'wikimedia_page_without_image', + }); + + if (!imageUrl || !imageUrl.startsWith('https://')) { + continue; + } + + return { + imageUrl, + landmarkName: summaryData.title || title, + placeId: null, + source: 'wikimedia', + fallbackReason: null, + }; + } catch (e: any) { + console.warn(`[DestinationImage] Exception encountered in Wikimedia query flow for host ${host}:`, e.message || e); } - - const summaryUrl = `https://${host}/api/rest_v1/page/summary/${encodeURIComponent(title)}`; - const summaryResponse = await fetch(summaryUrl); - const summaryData = (await summaryResponse.json()) as WikimediaPageSummary; - const imageUrl = summaryData.originalimage?.source || summaryData.thumbnail?.source || null; - - console.log('[DestinationImage] Wikimedia image result:', { - tripTitle: debugContext?.tripTitle || null, - destination: normalizedDestination, - host, - title: summaryData.title || title, - httpStatus: summaryResponse.status, - imageUrl, - fallbackReason: imageUrl ? null : 'wikimedia_page_without_image', - }); - - if (!summaryResponse.ok || !imageUrl?.startsWith('https://')) { - continue; - } - - return { - imageUrl, - landmarkName: summaryData.title || title, - placeId: null, - source: 'wikimedia', - fallbackReason: null, - }; } return null;