···250250251251 // Use the PDS endpoint if provided, otherwise use the default
252252 const apiUrl = pdsEndpoint ? `${pdsEndpoint}/xrpc` : DEFAULT_API_URL;
253253+ console.log(`Using API URL for profile fetch: ${apiUrl}`);
253254254254- // We can resolve either a handle or a DID
255255- let url;
256256- if (handle && handle.startsWith('did:')) {
257257- // If it's a DID, use describeRepo to get details
258258- url = `${apiUrl}/com.atproto.repo.describeRepo?repo=${encodeURIComponent(handle)}`;
259259- } else {
260260- // Otherwise treat it as a handle to resolve
261261- const userHandle = handle || 'atproto.com';
262262- url = `${apiUrl}/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(userHandle)}`;
263263- }
255255+ // Special handling for third-party PDS
256256+ // First get the user's DID if we're looking up a handle
257257+ let userDid = handle;
258258+ let userHandle = handle;
264259265265- const response = await fetch(url, {
266266- method: 'GET',
267267- headers: {
268268- 'Authorization': `DPoP ${accessToken}`,
269269- 'DPoP': dpopToken
260260+ try {
261261+ if (!handle.startsWith('did:')) {
262262+ // Always use bsky.social for resolving handles to DIDs
263263+ const resolveResponse = await fetch(`https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`);
264264+265265+ if (!resolveResponse.ok) {
266266+ console.error(`Failed to resolve handle ${handle}:`, await resolveResponse.text());
267267+ throw new Error(`Failed to resolve handle: ${resolveResponse.statusText}`);
268268+ }
269269+270270+ const resolveData = await resolveResponse.json();
271271+ userDid = resolveData.did;
272272+ userHandle = handle;
273273+ console.log(`Resolved handle ${handle} to DID ${userDid}`);
274274+ } else {
275275+ // If we're given a DID, try to find the handle
276276+ try {
277277+ // Try PLC directory first
278278+ const plcResponse = await fetch(`https://plc.directory/${handle}/data`);
279279+280280+ if (plcResponse.ok) {
281281+ const plcData = await plcResponse.json();
282282+ if (plcData.alsoKnownAs && plcData.alsoKnownAs.length > 0) {
283283+ const handleUrl = plcData.alsoKnownAs[0];
284284+ if (handleUrl.startsWith('at://')) {
285285+ userHandle = handleUrl.substring(5); // Remove 'at://'
286286+ console.log(`Resolved DID ${handle} to handle ${userHandle}`);
287287+ }
288288+ }
289289+ }
290290+ } catch (plcError) {
291291+ console.warn('Failed to resolve handle from PLC directory:', plcError);
292292+ }
270293 }
271271- });
272272-273273- // Check for DPoP nonce error
274274- if (response.status === 401) {
275275- const newNonce = response.headers.get('DPoP-Nonce');
276276- const errorResponse = await response.json().catch(() => ({}));
277294278278- if (newNonce) {
279279- return NextResponse.json({
280280- error: 'use_dpop_nonce',
281281- nonce: newNonce,
282282- originalError: errorResponse
283283- }, { status: 401 });
284284- }
285285- }
295295+ // Now use the correct endpoint for looking up the profile
296296+ const url = `${apiUrl}/com.atproto.repo.describeRepo?repo=${encodeURIComponent(userDid)}`;
286297287287- // Return the response
288288- if (!response.ok) {
289289- let errorText = '';
290290- let errorObj: Record<string, any> = {};
298298+ console.log(`Making profile request to: ${url}`);
299299+ const response = await fetch(url, {
300300+ method: 'GET',
301301+ headers: {
302302+ 'Authorization': `DPoP ${accessToken}`,
303303+ 'DPoP': dpopToken
304304+ }
305305+ });
291306292292- try {
293293- errorText = await response.text();
294294- console.error('Error from Bluesky:', response.status, errorText);
295295-296296- // Try to parse the response as JSON if it's not empty
297297- if (errorText) {
298298- try {
299299- errorObj = JSON.parse(errorText);
300300- } catch (parseError) {
301301- console.error('Failed to parse error response as JSON:', parseError);
302302- }
307307+ // Check for DPoP nonce error
308308+ if (response.status === 401) {
309309+ const newNonce = response.headers.get('DPoP-Nonce');
310310+ if (newNonce) {
311311+ console.log('Received nonce from profile request:', newNonce);
312312+ return NextResponse.json({
313313+ error: 'use_dpop_nonce',
314314+ nonce: newNonce
315315+ }, { status: 401 });
303316 }
304304- } catch (e) {
305305- console.error('Error reading response:', e);
306317 }
307318308308- // If we can't get the profile, return a basic response to continue the flow
309309- if (response.status === 400) {
319319+ // If we get a successful response
320320+ if (response.ok) {
321321+ const data = await response.json();
322322+ console.log('Successfully fetched profile data');
323323+324324+ // Return profile information with both DID and handle
310325 return NextResponse.json({
311311- did: 'unknown_did',
312312- handle: 'unknown'
326326+ did: userDid,
327327+ handle: userHandle
313328 });
314329 }
315330316316- return NextResponse.json(
317317- { error: 'Profile fetch error', message: errorText, details: errorObj },
318318- { status: response.status }
319319- );
331331+ // Handle error response
332332+ console.error(`Profile request failed with status: ${response.status}`);
333333+ let errorText = await response.text().catch(() => 'Failed to read response');
334334+ console.error('Error from profile request:', errorText);
335335+336336+ // Return information with the handle/DID we resolved
337337+ return NextResponse.json({
338338+ did: userDid,
339339+ handle: userHandle,
340340+ error: `Profile request failed with status: ${response.status}`
341341+ });
342342+343343+ } catch (error: any) {
344344+ console.error('Profile resolution error:', error);
345345+346346+ // Even if we have errors, return the best information we have
347347+ return NextResponse.json({
348348+ did: userDid || 'unknown_did',
349349+ handle: userHandle || 'unknown',
350350+ error: error.message
351351+ });
320352 }
353353+ } catch (outerError: any) {
354354+ console.error('Top-level profile fetch error:', outerError);
321355322322- const data = await response.json();
323323- return NextResponse.json(data);
324324- } catch (error: any) {
325325- console.error('Profile fetch error:', error);
326326- // Return a basic profile to continue the flow
356356+ // Fallback for any unexpected errors
327357 return NextResponse.json({
328358 did: 'unknown_did',
329329- handle: 'unknown'
359359+ handle: 'unknown',
360360+ error: outerError.message
330361 });
331362 }
332363}
+26-6
app/src/app/auth/callback/page.tsx
···156156157157 setStatus('Getting user profile...');
158158159159- // Extract PDS endpoint from the token
160160- let pdsEndpoint = null;
159159+ // Extract PDS endpoint from the token or from stored value
160160+ let pdsEndpoint = storedPdsEndpoint;
161161162162- // First, try to decode the access token
162162+ // First, try to decode the access token to extract the PDS endpoint
163163 try {
164164 const parts = tokenResponse.access_token.split('.');
165165 if (parts.length === 3) {
166166 const payload = JSON.parse(atob(parts[1]));
167167- if (payload.aud && typeof payload.aud === 'string' && payload.aud.startsWith('did:web:')) {
168168- pdsEndpoint = 'https://' + payload.aud.replace('did:web:', '');
167167+ console.log('Token payload:', {
168168+ ...payload,
169169+ access_token: tokenResponse.access_token ? '[REDACTED]' : null
170170+ });
171171+172172+ if (payload.aud && typeof payload.aud === 'string') {
173173+ if (payload.aud.startsWith('did:web:')) {
174174+ pdsEndpoint = 'https://' + payload.aud.replace('did:web:', '');
175175+ console.log('Extracted PDS endpoint from token did:web aud:', pdsEndpoint);
176176+ } else if (payload.aud.startsWith('https://')) {
177177+ pdsEndpoint = payload.aud;
178178+ console.log('Using https:// aud as PDS endpoint:', pdsEndpoint);
179179+ } else if (payload.iss && payload.iss.startsWith('https://')) {
180180+ pdsEndpoint = payload.iss;
181181+ console.log('Using iss as PDS endpoint:', pdsEndpoint);
182182+ }
169183 }
170184 }
171185 } catch (e) {
172172- console.warn('Failed to extract PDS endpoint from token');
186186+ console.warn('Failed to extract PDS endpoint from token:', e);
187187+188188+ // If we couldn't extract from token but have the stored endpoint, use that
189189+ if (storedPdsEndpoint) {
190190+ console.log('Using stored PDS endpoint instead:', storedPdsEndpoint);
191191+ pdsEndpoint = storedPdsEndpoint;
192192+ }
173193 }
174194175195 // Get user profile
+1-1
app/src/lib/bluesky-api.ts
···111111 const baseUrl = pdsEndpoint ? `${pdsEndpoint}/xrpc` : DEFAULT_API_URL;
112112 const url = `${baseUrl}/${endpoint}`;
113113114114- console.log(`Making ${method} request to ${url}`);
114114+ console.log(`Making ${method} request to ${url} (PDS: ${pdsEndpoint || 'default'})`);
115115116116 // If no nonce is provided, try to get one first
117117 if (!dpopNonce) {