This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

fix auth

+118 -67
+91 -60
app/src/app/api/bluesky/profile/route.ts
··· 250 250 251 251 // Use the PDS endpoint if provided, otherwise use the default 252 252 const apiUrl = pdsEndpoint ? `${pdsEndpoint}/xrpc` : DEFAULT_API_URL; 253 + console.log(`Using API URL for profile fetch: ${apiUrl}`); 253 254 254 - // We can resolve either a handle or a DID 255 - let url; 256 - if (handle && handle.startsWith('did:')) { 257 - // If it's a DID, use describeRepo to get details 258 - url = `${apiUrl}/com.atproto.repo.describeRepo?repo=${encodeURIComponent(handle)}`; 259 - } else { 260 - // Otherwise treat it as a handle to resolve 261 - const userHandle = handle || 'atproto.com'; 262 - url = `${apiUrl}/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(userHandle)}`; 263 - } 255 + // Special handling for third-party PDS 256 + // First get the user's DID if we're looking up a handle 257 + let userDid = handle; 258 + let userHandle = handle; 264 259 265 - const response = await fetch(url, { 266 - method: 'GET', 267 - headers: { 268 - 'Authorization': `DPoP ${accessToken}`, 269 - 'DPoP': dpopToken 260 + try { 261 + if (!handle.startsWith('did:')) { 262 + // Always use bsky.social for resolving handles to DIDs 263 + const resolveResponse = await fetch(`https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`); 264 + 265 + if (!resolveResponse.ok) { 266 + console.error(`Failed to resolve handle ${handle}:`, await resolveResponse.text()); 267 + throw new Error(`Failed to resolve handle: ${resolveResponse.statusText}`); 268 + } 269 + 270 + const resolveData = await resolveResponse.json(); 271 + userDid = resolveData.did; 272 + userHandle = handle; 273 + console.log(`Resolved handle ${handle} to DID ${userDid}`); 274 + } else { 275 + // If we're given a DID, try to find the handle 276 + try { 277 + // Try PLC directory first 278 + const plcResponse = await fetch(`https://plc.directory/${handle}/data`); 279 + 280 + if (plcResponse.ok) { 281 + const plcData = await plcResponse.json(); 282 + if (plcData.alsoKnownAs && plcData.alsoKnownAs.length > 0) { 283 + const handleUrl = plcData.alsoKnownAs[0]; 284 + if (handleUrl.startsWith('at://')) { 285 + userHandle = handleUrl.substring(5); // Remove 'at://' 286 + console.log(`Resolved DID ${handle} to handle ${userHandle}`); 287 + } 288 + } 289 + } 290 + } catch (plcError) { 291 + console.warn('Failed to resolve handle from PLC directory:', plcError); 292 + } 270 293 } 271 - }); 272 - 273 - // Check for DPoP nonce error 274 - if (response.status === 401) { 275 - const newNonce = response.headers.get('DPoP-Nonce'); 276 - const errorResponse = await response.json().catch(() => ({})); 277 294 278 - if (newNonce) { 279 - return NextResponse.json({ 280 - error: 'use_dpop_nonce', 281 - nonce: newNonce, 282 - originalError: errorResponse 283 - }, { status: 401 }); 284 - } 285 - } 295 + // Now use the correct endpoint for looking up the profile 296 + const url = `${apiUrl}/com.atproto.repo.describeRepo?repo=${encodeURIComponent(userDid)}`; 286 297 287 - // Return the response 288 - if (!response.ok) { 289 - let errorText = ''; 290 - let errorObj: Record<string, any> = {}; 298 + console.log(`Making profile request to: ${url}`); 299 + const response = await fetch(url, { 300 + method: 'GET', 301 + headers: { 302 + 'Authorization': `DPoP ${accessToken}`, 303 + 'DPoP': dpopToken 304 + } 305 + }); 291 306 292 - try { 293 - errorText = await response.text(); 294 - console.error('Error from Bluesky:', response.status, errorText); 295 - 296 - // Try to parse the response as JSON if it's not empty 297 - if (errorText) { 298 - try { 299 - errorObj = JSON.parse(errorText); 300 - } catch (parseError) { 301 - console.error('Failed to parse error response as JSON:', parseError); 302 - } 307 + // Check for DPoP nonce error 308 + if (response.status === 401) { 309 + const newNonce = response.headers.get('DPoP-Nonce'); 310 + if (newNonce) { 311 + console.log('Received nonce from profile request:', newNonce); 312 + return NextResponse.json({ 313 + error: 'use_dpop_nonce', 314 + nonce: newNonce 315 + }, { status: 401 }); 303 316 } 304 - } catch (e) { 305 - console.error('Error reading response:', e); 306 317 } 307 318 308 - // If we can't get the profile, return a basic response to continue the flow 309 - if (response.status === 400) { 319 + // If we get a successful response 320 + if (response.ok) { 321 + const data = await response.json(); 322 + console.log('Successfully fetched profile data'); 323 + 324 + // Return profile information with both DID and handle 310 325 return NextResponse.json({ 311 - did: 'unknown_did', 312 - handle: 'unknown' 326 + did: userDid, 327 + handle: userHandle 313 328 }); 314 329 } 315 330 316 - return NextResponse.json( 317 - { error: 'Profile fetch error', message: errorText, details: errorObj }, 318 - { status: response.status } 319 - ); 331 + // Handle error response 332 + console.error(`Profile request failed with status: ${response.status}`); 333 + let errorText = await response.text().catch(() => 'Failed to read response'); 334 + console.error('Error from profile request:', errorText); 335 + 336 + // Return information with the handle/DID we resolved 337 + return NextResponse.json({ 338 + did: userDid, 339 + handle: userHandle, 340 + error: `Profile request failed with status: ${response.status}` 341 + }); 342 + 343 + } catch (error: any) { 344 + console.error('Profile resolution error:', error); 345 + 346 + // Even if we have errors, return the best information we have 347 + return NextResponse.json({ 348 + did: userDid || 'unknown_did', 349 + handle: userHandle || 'unknown', 350 + error: error.message 351 + }); 320 352 } 353 + } catch (outerError: any) { 354 + console.error('Top-level profile fetch error:', outerError); 321 355 322 - const data = await response.json(); 323 - return NextResponse.json(data); 324 - } catch (error: any) { 325 - console.error('Profile fetch error:', error); 326 - // Return a basic profile to continue the flow 356 + // Fallback for any unexpected errors 327 357 return NextResponse.json({ 328 358 did: 'unknown_did', 329 - handle: 'unknown' 359 + handle: 'unknown', 360 + error: outerError.message 330 361 }); 331 362 } 332 363 }
+26 -6
app/src/app/auth/callback/page.tsx
··· 156 156 157 157 setStatus('Getting user profile...'); 158 158 159 - // Extract PDS endpoint from the token 160 - let pdsEndpoint = null; 159 + // Extract PDS endpoint from the token or from stored value 160 + let pdsEndpoint = storedPdsEndpoint; 161 161 162 - // First, try to decode the access token 162 + // First, try to decode the access token to extract the PDS endpoint 163 163 try { 164 164 const parts = tokenResponse.access_token.split('.'); 165 165 if (parts.length === 3) { 166 166 const payload = JSON.parse(atob(parts[1])); 167 - if (payload.aud && typeof payload.aud === 'string' && payload.aud.startsWith('did:web:')) { 168 - pdsEndpoint = 'https://' + payload.aud.replace('did:web:', ''); 167 + console.log('Token payload:', { 168 + ...payload, 169 + access_token: tokenResponse.access_token ? '[REDACTED]' : null 170 + }); 171 + 172 + if (payload.aud && typeof payload.aud === 'string') { 173 + if (payload.aud.startsWith('did:web:')) { 174 + pdsEndpoint = 'https://' + payload.aud.replace('did:web:', ''); 175 + console.log('Extracted PDS endpoint from token did:web aud:', pdsEndpoint); 176 + } else if (payload.aud.startsWith('https://')) { 177 + pdsEndpoint = payload.aud; 178 + console.log('Using https:// aud as PDS endpoint:', pdsEndpoint); 179 + } else if (payload.iss && payload.iss.startsWith('https://')) { 180 + pdsEndpoint = payload.iss; 181 + console.log('Using iss as PDS endpoint:', pdsEndpoint); 182 + } 169 183 } 170 184 } 171 185 } catch (e) { 172 - console.warn('Failed to extract PDS endpoint from token'); 186 + console.warn('Failed to extract PDS endpoint from token:', e); 187 + 188 + // If we couldn't extract from token but have the stored endpoint, use that 189 + if (storedPdsEndpoint) { 190 + console.log('Using stored PDS endpoint instead:', storedPdsEndpoint); 191 + pdsEndpoint = storedPdsEndpoint; 192 + } 173 193 } 174 194 175 195 // Get user profile
+1 -1
app/src/lib/bluesky-api.ts
··· 111 111 const baseUrl = pdsEndpoint ? `${pdsEndpoint}/xrpc` : DEFAULT_API_URL; 112 112 const url = `${baseUrl}/${endpoint}`; 113 113 114 - console.log(`Making ${method} request to ${url}`); 114 + console.log(`Making ${method} request to ${url} (PDS: ${pdsEndpoint || 'default'})`); 115 115 116 116 // If no nonce is provided, try to get one first 117 117 if (!dpopNonce) {