test oauth pds
This commit is contained in:
		
							
								
								
									
										293
									
								
								oauth/src/utils/pds-detection.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										293
									
								
								oauth/src/utils/pds-detection.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,293 @@ | ||||
| // PDS Detection and API URL mapping utilities | ||||
|  | ||||
| export interface NetworkConfig { | ||||
|   pdsApi: string; | ||||
|   plcApi: string; | ||||
|   bskyApi: string; | ||||
|   webUrl: string; | ||||
| } | ||||
|  | ||||
| // Detect PDS from handle | ||||
| export function detectPdsFromHandle(handle: string): string { | ||||
|   if (handle.endsWith('.syu.is')) { | ||||
|     return 'syu.is'; | ||||
|   } | ||||
|   if (handle.endsWith('.bsky.social') || handle.endsWith('.bsky.app')) { | ||||
|     return 'bsky.social'; | ||||
|   } | ||||
|   // Default to Bluesky for unknown domains | ||||
|   return 'bsky.social'; | ||||
| } | ||||
|  | ||||
| // Map PDS endpoint to network configuration | ||||
| export function getNetworkConfigFromPdsEndpoint(pdsEndpoint: string): NetworkConfig { | ||||
|   try { | ||||
|     const url = new URL(pdsEndpoint); | ||||
|     const hostname = url.hostname; | ||||
|      | ||||
|     // Map based on actual PDS endpoint | ||||
|     if (hostname === 'syu.is') { | ||||
|       return { | ||||
|         pdsApi: 'https://syu.is',           // PDS API (repo operations) | ||||
|         plcApi: 'https://plc.syu.is',       // PLC directory | ||||
|         bskyApi: 'https://bsky.syu.is',     // Bluesky API (getProfile, etc.) | ||||
|         webUrl: 'https://web.syu.is'        // Web interface | ||||
|       }; | ||||
|     } else if (hostname.includes('bsky.network') || hostname === 'bsky.social' || hostname.includes('host.bsky.network')) { | ||||
|       // All Bluesky infrastructure (including *.host.bsky.network) | ||||
|       return { | ||||
|         pdsApi: pdsEndpoint,                      // Use actual PDS endpoint (e.g., shiitake.us-east.host.bsky.network) | ||||
|         plcApi: 'https://plc.directory',          // Standard PLC directory | ||||
|         bskyApi: 'https://public.api.bsky.app',   // Bluesky public API (NOT PDS) | ||||
|         webUrl: 'https://bsky.app'                // Bluesky web interface | ||||
|       }; | ||||
|     } else { | ||||
|       // Unknown PDS, assume Bluesky-compatible but use PDS for repo operations | ||||
|       return { | ||||
|         pdsApi: pdsEndpoint,                      // Use actual PDS for repo ops | ||||
|         plcApi: 'https://plc.directory',          // Default PLC | ||||
|         bskyApi: 'https://public.api.bsky.app',   // Default to Bluesky API | ||||
|         webUrl: 'https://bsky.app'                // Default web interface | ||||
|       }; | ||||
|     } | ||||
|   } catch (error) { | ||||
|     // Fallback for invalid URLs | ||||
|     return { | ||||
|       pdsApi: 'https://bsky.social', | ||||
|       plcApi: 'https://plc.directory', | ||||
|       bskyApi: 'https://public.api.bsky.app', | ||||
|       webUrl: 'https://bsky.app' | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Legacy function for backwards compatibility | ||||
| export function getNetworkConfig(pds: string): NetworkConfig { | ||||
|   // This now assumes pds is a hostname | ||||
|   return getNetworkConfigFromPdsEndpoint(`https://${pds}`); | ||||
| } | ||||
|  | ||||
| // Get appropriate API URL for a user based on their handle | ||||
| export function getApiUrlForUser(handle: string): string { | ||||
|   const pds = detectPdsFromHandle(handle); | ||||
|   const config = getNetworkConfig(pds); | ||||
|   return config.bskyApi; | ||||
| } | ||||
|  | ||||
| // Resolve handle/DID to actual PDS endpoint using com.atproto.repo.describeRepo | ||||
| export async function resolvePdsFromRepo(handleOrDid: string): Promise<{ pds: string; did: string; handle: string }> { | ||||
|   let targetDid = handleOrDid; | ||||
|   let targetHandle = handleOrDid; | ||||
|    | ||||
|   // If handle provided, resolve to DID first using identity.resolveHandle | ||||
|   if (!handleOrDid.startsWith('did:')) { | ||||
|     try { | ||||
|       // Try multiple endpoints for handle resolution | ||||
|       const resolveEndpoints = ['https://public.api.bsky.app', 'https://bsky.syu.is']; | ||||
|       let resolved = false; | ||||
|        | ||||
|       for (const endpoint of resolveEndpoints) { | ||||
|         try { | ||||
|           const resolveResponse = await fetch(`${endpoint}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handleOrDid)}`); | ||||
|           if (resolveResponse.ok) { | ||||
|             const resolveData = await resolveResponse.json(); | ||||
|             targetDid = resolveData.did; | ||||
|             resolved = true; | ||||
|             break; | ||||
|           } | ||||
|         } catch (error) { | ||||
|           continue; | ||||
|         } | ||||
|       } | ||||
|        | ||||
|       if (!resolved) { | ||||
|         throw new Error('Handle resolution failed from all endpoints'); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       throw new Error(`Failed to resolve handle ${handleOrDid} to DID: ${error}`); | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   // Now use com.atproto.repo.describeRepo to get PDS from known PDS endpoints | ||||
|   const pdsEndpoints = ['https://bsky.social', 'https://syu.is']; | ||||
|    | ||||
|   for (const pdsEndpoint of pdsEndpoints) { | ||||
|     try { | ||||
|       const response = await fetch(`${pdsEndpoint}/xrpc/com.atproto.repo.describeRepo?repo=${encodeURIComponent(targetDid)}`); | ||||
|        | ||||
|       if (response.ok) { | ||||
|         const data = await response.json(); | ||||
|          | ||||
|         // Extract PDS from didDoc.service | ||||
|         const services = data.didDoc?.service || []; | ||||
|         const pdsService = services.find((s: any) =>  | ||||
|           s.id === '#atproto_pds' || s.type === 'AtprotoPersonalDataServer' | ||||
|         ); | ||||
|          | ||||
|         if (pdsService) { | ||||
|           return { | ||||
|             pds: pdsService.serviceEndpoint, | ||||
|             did: data.did || targetDid, | ||||
|             handle: data.handle || targetHandle | ||||
|           }; | ||||
|         } | ||||
|       } | ||||
|     } catch (error) { | ||||
|       continue; | ||||
|     } | ||||
|   } | ||||
|    | ||||
|   throw new Error(`Failed to resolve PDS for ${handleOrDid} from any endpoint`); | ||||
| } | ||||
|  | ||||
| // Resolve DID to actual PDS endpoint using com.atproto.repo.describeRepo | ||||
| export async function resolvePdsFromDid(did: string): Promise<string> { | ||||
|   const resolved = await resolvePdsFromRepo(did); | ||||
|   return resolved.pds; | ||||
| } | ||||
|  | ||||
| // Enhanced resolve handle to DID with proper PDS detection | ||||
| export async function resolveHandleToDid(handle: string): Promise<{ did: string; pds: string }> { | ||||
|   try { | ||||
|     // First, try to resolve the handle to DID using multiple methods | ||||
|     const apiUrl = getApiUrlForUser(handle); | ||||
|     const response = await fetch(`${apiUrl}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(handle)}`); | ||||
|      | ||||
|     if (!response.ok) { | ||||
|       throw new Error(`Failed to resolve handle: ${response.status}`); | ||||
|     } | ||||
|      | ||||
|     const data = await response.json(); | ||||
|     const did = data.did; | ||||
|      | ||||
|     // Now resolve the actual PDS from the DID | ||||
|     const actualPds = await resolvePdsFromDid(did); | ||||
|      | ||||
|     return { | ||||
|       did: did, | ||||
|       pds: actualPds | ||||
|     }; | ||||
|   } catch (error) { | ||||
|     console.error(`Failed to resolve handle ${handle}:`, error); | ||||
|      | ||||
|     // Fallback to handle-based detection | ||||
|     const fallbackPds = detectPdsFromHandle(handle); | ||||
|     throw error; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Get profile using appropriate API for the user with accurate PDS resolution | ||||
| export async function getProfileForUser(handleOrDid: string, knownPdsEndpoint?: string): Promise<any> { | ||||
|   try { | ||||
|     let apiUrl: string; | ||||
|      | ||||
|     if (knownPdsEndpoint) { | ||||
|       // If we already know the user's PDS endpoint, use it directly | ||||
|       const config = getNetworkConfigFromPdsEndpoint(knownPdsEndpoint); | ||||
|       apiUrl = config.bskyApi; | ||||
|     } else { | ||||
|       // Resolve the user's actual PDS using describeRepo | ||||
|       try { | ||||
|         const resolved = await resolvePdsFromRepo(handleOrDid); | ||||
|         const config = getNetworkConfigFromPdsEndpoint(resolved.pds); | ||||
|         apiUrl = config.bskyApi; | ||||
|       } catch { | ||||
|         // Fallback to handle-based detection | ||||
|         apiUrl = getApiUrlForUser(handleOrDid); | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     const response = await fetch(`${apiUrl}/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(handleOrDid)}`); | ||||
|     if (!response.ok) { | ||||
|       throw new Error(`Failed to get profile: ${response.status}`); | ||||
|     } | ||||
|      | ||||
|     return await response.json(); | ||||
|   } catch (error) { | ||||
|     console.error(`Failed to get profile for ${handleOrDid}:`, error); | ||||
|      | ||||
|     // Final fallback: try with default Bluesky API | ||||
|     try { | ||||
|       const response = await fetch(`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${encodeURIComponent(handleOrDid)}`); | ||||
|       if (response.ok) { | ||||
|         return await response.json(); | ||||
|       } | ||||
|     } catch { | ||||
|       // Ignore fallback errors | ||||
|     } | ||||
|      | ||||
|     throw error; | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Test and verify PDS detection methods | ||||
| export async function verifyPdsDetection(handleOrDid: string): Promise<void> { | ||||
|   try { | ||||
|     // Method 1: com.atproto.repo.describeRepo (PRIMARY METHOD) | ||||
|     try { | ||||
|       const resolved = await resolvePdsFromRepo(handleOrDid); | ||||
|       const config = getNetworkConfigFromPdsEndpoint(resolved.pds); | ||||
|     } catch (error) { | ||||
|       // describeRepo failed | ||||
|     } | ||||
|      | ||||
|     // Method 2: com.atproto.identity.resolveHandle (for comparison) | ||||
|     if (!handleOrDid.startsWith('did:')) { | ||||
|       try { | ||||
|         const resolveResponse = await fetch(`https://public.api.bsky.app/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handleOrDid)}`); | ||||
|         if (resolveResponse.ok) { | ||||
|           const resolveData = await resolveResponse.json(); | ||||
|         } | ||||
|       } catch (error) { | ||||
|         // Error resolving handle | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     // Method 3: PLC Directory lookup (if we have a DID) | ||||
|     let targetDid = handleOrDid; | ||||
|     if (!handleOrDid.startsWith('did:')) { | ||||
|       try { | ||||
|         const profile = await getProfileForUser(handleOrDid); | ||||
|         targetDid = profile.did; | ||||
|       } catch { | ||||
|         return; | ||||
|       } | ||||
|     } | ||||
|      | ||||
|     try { | ||||
|       const plcResponse = await fetch(`https://plc.directory/${targetDid}`); | ||||
|       if (plcResponse.ok) { | ||||
|         const didDocument = await plcResponse.json(); | ||||
|          | ||||
|         // Find PDS service | ||||
|         const pdsService = didDocument.service?.find((s: any) =>  | ||||
|           s.id === '#atproto_pds' || s.type === 'AtprotoPersonalDataServer' | ||||
|         ); | ||||
|          | ||||
|         if (pdsService) { | ||||
|           // Try to detect if this is a known network | ||||
|           const pdsUrl = pdsService.serviceEndpoint; | ||||
|           const hostname = new URL(pdsUrl).hostname; | ||||
|           const detectedNetwork = detectPdsFromHandle(`user.${hostname}`); | ||||
|           const networkConfig = getNetworkConfig(hostname); | ||||
|         } | ||||
|       } | ||||
|     } catch (error) { | ||||
|       // Error fetching from PLC directory | ||||
|     } | ||||
|      | ||||
|     // Method 4: Our enhanced resolution | ||||
|     try { | ||||
|       if (handleOrDid.startsWith('did:')) { | ||||
|         const pdsEndpoint = await resolvePdsFromDid(handleOrDid); | ||||
|       } else { | ||||
|         const resolved = await resolveHandleToDid(handleOrDid); | ||||
|       } | ||||
|     } catch (error) { | ||||
|       // Enhanced resolution failed | ||||
|     } | ||||
|      | ||||
|   } catch (error) { | ||||
|     // Overall verification failed | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user