WIP save Spotify login progress

This commit is contained in:
2026-05-24 21:17:20 +01:00
parent dcfc8d4a54
commit 6e51d251dd
4 changed files with 167 additions and 62 deletions

View File

@@ -24,7 +24,7 @@ export default function LoginScreen({ navigation }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// Direct Spotify App Client ID // 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 // Configure Dynamic Redirect URI
const redirectUri = makeRedirectUri({ const redirectUri = makeRedirectUri({
@@ -42,6 +42,13 @@ export default function LoginScreen({ navigation }) {
discovery 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 = () => { const showSpotifyLoginFailure = () => {
Alert.alert('Erro de Autenticação', 'Não foi possível iniciar sessão com Spotify. Tenta novamente ou usa login normal.'); 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 // Handle Spotify OAuth Response
useEffect(() => { useEffect(() => {
if (response) { 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') { 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:", { console.log("[LoginScreen] Spotify auth success:", {
redirectUri, redirectUri,
clientIdExists: Boolean(SPOTIFY_CLIENT_ID), clientIdExists: Boolean(SPOTIFY_CLIENT_ID),
@@ -61,15 +88,19 @@ export default function LoginScreen({ navigation }) {
flow: 'authorization_code_pkce', flow: 'authorization_code_pkce',
}); });
exchangeCodeForTokens(code); exchangeCodeForTokens(code);
} else if (response.type === 'error') { } else {
console.warn('[LoginScreen] Spotify OAuth response error:', response.error?.message || response.error); console.warn("[SpotifyAuthDebug] Stage: After redirecting back - Auth session ended without success status. Type:", response.type);
Alert.alert('Erro de Autenticação', response.error?.message || 'Falha ao logar com o Spotify'); 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]); }, [response]);
const exchangeCodeForTokens = async (code: string) => { const exchangeCodeForTokens = async (code: string) => {
if (!request?.codeVerifier) { if (!request?.codeVerifier) {
console.warn('[SpotifyAuthDebug] Stage: Token Exchange Failed (Missing PKCE code verifier)');
console.warn('[LoginScreen] Missing Spotify PKCE code verifier:', { console.warn('[LoginScreen] Missing Spotify PKCE code verifier:', {
redirectUri, redirectUri,
clientIdExists: Boolean(SPOTIFY_CLIENT_ID), clientIdExists: Boolean(SPOTIFY_CLIENT_ID),
@@ -82,6 +113,7 @@ export default function LoginScreen({ navigation }) {
try { try {
setLoading(true); setLoading(true);
console.log('[SpotifyAuthDebug] Stage: Performing Token Exchange');
console.log('[LoginScreen] Spotify token exchange start:', { console.log('[LoginScreen] Spotify token exchange start:', {
redirectUri, redirectUri,
clientIdExists: Boolean(SPOTIFY_CLIENT_ID), clientIdExists: Boolean(SPOTIFY_CLIENT_ID),
@@ -114,6 +146,7 @@ export default function LoginScreen({ navigation }) {
}); });
if (!tokenResponse.ok) { if (!tokenResponse.ok) {
console.warn('[SpotifyAuthDebug] Stage: Token Exchange Failed (HTTP Error)');
console.warn('[LoginScreen] Spotify token exchange failed:', { console.warn('[LoginScreen] Spotify token exchange failed:', {
httpStatus: tokenResponse.status, httpStatus: tokenResponse.status,
responseBody: tokenResponseText, responseBody: tokenResponseText,
@@ -126,6 +159,7 @@ export default function LoginScreen({ navigation }) {
try { try {
tokenResult = JSON.parse(tokenResponseText); tokenResult = JSON.parse(tokenResponseText);
} catch (parseError) { } catch (parseError) {
console.warn('[SpotifyAuthDebug] Stage: Token Exchange Failed (JSON Parse Error)');
console.warn('[LoginScreen] Spotify token response JSON parse failed:', parseError); console.warn('[LoginScreen] Spotify token response JSON parse failed:', parseError);
showSpotifyLoginFailure(); showSpotifyLoginFailure();
return; return;
@@ -139,6 +173,7 @@ export default function LoginScreen({ navigation }) {
await setSpotifyRefreshToken(tokenResult.refresh_token); await setSpotifyRefreshToken(tokenResult.refresh_token);
} }
console.log("[SpotifyAuthDebug] Stage: Fetching Spotify Profile");
console.log("SPOTIFY_PROFILE_FETCH_START"); console.log("SPOTIFY_PROFILE_FETCH_START");
const profileRes = await fetch('https://api.spotify.com/v1/me', { const profileRes = await fetch('https://api.spotify.com/v1/me', {
headers: { Authorization: `Bearer ${tokenResult.access_token}` } headers: { Authorization: `Bearer ${tokenResult.access_token}` }
@@ -152,6 +187,7 @@ export default function LoginScreen({ navigation }) {
}); });
if (!profileRes.ok) { if (!profileRes.ok) {
console.warn('[SpotifyAuthDebug] Stage: Fetching Spotify Profile Failed (HTTP Error)');
console.warn('[LoginScreen] Spotify profile fetch failed:', { console.warn('[LoginScreen] Spotify profile fetch failed:', {
httpStatus: profileRes.status, httpStatus: profileRes.status,
responseBody: profileText, responseBody: profileText,
@@ -164,11 +200,13 @@ export default function LoginScreen({ navigation }) {
try { try {
profile = JSON.parse(profileText); profile = JSON.parse(profileText);
} catch (parseError) { } catch (parseError) {
console.warn('[SpotifyAuthDebug] Stage: Fetching Spotify Profile Failed (JSON Parse Error)');
console.warn('[LoginScreen] Spotify profile JSON parse failed:', parseError); console.warn('[LoginScreen] Spotify profile JSON parse failed:', parseError);
showSpotifyLoginFailure(); showSpotifyLoginFailure();
return; return;
} }
console.log("[SpotifyAuthDebug] Stage: Completing Supabase Auth and updating metadata");
console.log("SPOTIFY_PROFILE_NAME:", profile.display_name); console.log("SPOTIFY_PROFILE_NAME:", profile.display_name);
console.log("SPOTIFY_PROFILE_EMAIL_EXISTS:", !!profile.email); console.log("SPOTIFY_PROFILE_EMAIL_EXISTS:", !!profile.email);
console.log("SPOTIFY_USER_ID:", profile.id); console.log("SPOTIFY_USER_ID:", profile.id);
@@ -277,6 +315,8 @@ export default function LoginScreen({ navigation }) {
const handleSpotifyLogin = async () => { const handleSpotifyLogin = async () => {
try { 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:", { console.log("[LoginScreen] Spotify login start:", {
redirectUri, redirectUri,
clientIdExists: Boolean(SPOTIFY_CLIENT_ID), clientIdExists: Boolean(SPOTIFY_CLIENT_ID),
@@ -285,7 +325,9 @@ export default function LoginScreen({ navigation }) {
flow: 'authorization_code_pkce', flow: 'authorization_code_pkce',
}); });
await promptAsync(); await promptAsync();
console.log("[SpotifyAuthDebug] Browser promptAsync promise resolved.");
} catch (e: any) { } 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); console.warn('🚀 [LoginScreen] OAuth Error:', e?.message || e);
showSpotifyLoginFailure(); showSpotifyLoginFailure();
} }

View File

@@ -26,7 +26,7 @@ export default function RegisterScreen({ navigation }) {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
// Direct Spotify App Client ID // 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 // Configure Dynamic Redirect URI
const redirectUri = makeRedirectUri({ const redirectUri = makeRedirectUri({

View File

@@ -80,7 +80,7 @@ export default function NewTripScreen({ navigation }) {
if (providerToken) { if (providerToken) {
// Proactively check if token is valid, or refresh it // 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', { let testRes = await fetch('https://api.spotify.com/v1/me', {
headers: { Authorization: `Bearer ${providerToken}` } headers: { Authorization: `Bearer ${providerToken}` }
}); });
@@ -90,19 +90,17 @@ export default function NewTripScreen({ navigation }) {
const newToken = await refreshSpotifyToken(); const newToken = await refreshSpotifyToken();
if (newToken) { if (newToken) {
providerToken = newToken; providerToken = newToken;
console.log("Spotify token refreshed successfully!");
} else { } else {
console.log("Failed to refresh Spotify token."); console.log("Failed to refresh Spotify token.");
providerToken = null; providerToken = null;
} }
} else if (!testRes.ok) { } else if (!testRes.ok) {
const testErr = await testRes.text(); const testErr = await testRes.text();
if (testRes.status === 403 || testErr.toLowerCase().includes('active premium subscription required')) { console.warn("Spotify validation request failed on GET /v1/me:", testRes.status, testErr);
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);
}
providerToken = null; 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 // 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`, { const createPlaylistRes = await fetch(`https://api.spotify.com/v1/users/${spotifyUserId}/playlists`, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -166,7 +168,21 @@ export default function NewTripScreen({ navigation }) {
public: false 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'); if (!playlistData.id) throw new Error('Could not create playlist');
const playlistId = playlistData.id; const playlistId = playlistData.id;
generatedPlaylistUrl = playlistData.external_urls.spotify; generatedPlaylistUrl = playlistData.external_urls.spotify;
@@ -225,6 +241,8 @@ export default function NewTripScreen({ navigation }) {
const chunkSize = 100; const chunkSize = 100;
for (let i = 0; i < trackUris.length; i += chunkSize) { for (let i = 0; i < trackUris.length; i += chunkSize) {
const chunk = trackUris.slice(i, 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`, { const addTracksRes = await fetch(`https://api.spotify.com/v1/playlists/${playlistId}/tracks`, {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -233,8 +251,11 @@ export default function NewTripScreen({ navigation }) {
}, },
body: JSON.stringify({ uris: chunk }) body: JSON.stringify({ uris: chunk })
}); });
console.log("[SpotifyPlaylistDebug] add tracks HTTP status:", addTracksRes.status);
if (!addTracksRes.ok) { if (!addTracksRes.ok) {
const addTracksErr = await addTracksRes.text(); 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')) { if (addTracksRes.status === 403 || addTracksErr.toLowerCase().includes('active premium subscription required')) {
spotifyPremiumRequired = true; spotifyPremiumRequired = true;
console.warn("Spotify add tracks skipped due to expected Premium limitation:", addTracksRes.status, addTracksErr); console.warn("Spotify add tracks skipped due to expected Premium limitation:", addTracksRes.status, addTracksErr);

View File

@@ -97,6 +97,7 @@ const getGooglePlacesImage = async (normalizedDestination: string, debugContext?
destination: normalizedDestination, destination: normalizedDestination,
apiKeyExists: Boolean(GOOGLE_MAPS_API_KEY), 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) { if (!GOOGLE_MAPS_API_KEY) {
console.warn('[DestinationImage] Google Places skipped: missing 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 response = await fetch(url);
const data = (await response.json()) as PlacesSearchResponse; 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:', { console.log('[DestinationImage] Google Places result:', {
tripTitle: debugContext?.tripTitle || null, tripTitle: debugContext?.tripTitle || null,
destination: normalizedDestination, destination: normalizedDestination,
@@ -190,57 +194,95 @@ const getWikimediaImage = async (normalizedDestination: string, debugContext?: D
const languageHosts = ['pt.wikipedia.org', 'en.wikipedia.org']; const languageHosts = ['pt.wikipedia.org', 'en.wikipedia.org'];
for (const host of languageHosts) { for (const host of languageHosts) {
const searchUrl = `https://${host}/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(normalizedDestination)}&format=json&origin=*&srlimit=1`; try {
console.log('[DestinationImage] Wikimedia search:', { const searchUrl = `https://${host}/w/api.php?action=query&list=search&srsearch=${encodeURIComponent(normalizedDestination)}&format=json&origin=*&srlimit=1`;
tripTitle: debugContext?.tripTitle || null, console.log('[DestinationImage] Wikimedia search:', {
destination: normalizedDestination, tripTitle: debugContext?.tripTitle || null,
host, destination: normalizedDestination,
url: searchUrl, host,
}); url: searchUrl,
});
const searchResponse = await fetch(searchUrl); const searchResponse = await fetch(searchUrl);
const searchData = (await searchResponse.json()) as WikimediaSearchResponse; if (!searchResponse.ok) {
const title = searchData.query?.search?.[0]?.title; console.warn(`[DestinationImage] Wikimedia search request failed for host ${host} with status:`, searchResponse.status);
continue;
}
console.log('[DestinationImage] Wikimedia search result:', { const searchContentType = searchResponse.headers.get("content-type") || "";
tripTitle: debugContext?.tripTitle || null, if (!searchContentType.includes("application/json")) {
destination: normalizedDestination, const text = await searchResponse.text();
host, console.warn(`[DestinationImage] Wikimedia search returned non-JSON for host ${host}:`, text.substring(0, 100));
httpStatus: searchResponse.status, continue;
title: title || null, }
body: searchData,
});
if (!searchResponse.ok || !title) { const searchData = (await searchResponse.json()) as WikimediaSearchResponse;
continue; 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; return null;