WIP save Spotify login progress
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user