This repository has no description
0

Configure Feed

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

fix

+124 -39
+114 -38
app/src/app/api/bluesky/feed/route.ts
··· 34 34 const MAX_ENTRIES = 20; 35 35 36 36 // Cache settings to avoid hitting the database too frequently 37 - const CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds 37 + const CACHE_TTL = 1 * 60 * 1000; // 1 minute in milliseconds (reduced from 5 min) 38 38 let cachedEntries: ProcessedEntry[] = []; 39 39 let lastFetchTime = 0; 40 40 ··· 123 123 } 124 124 } 125 125 126 - // For normal (non-pagination) requests, use the cache if valid 126 + // For normal (non-pagination) requests, use the cache if valid and not forcing refresh 127 127 if (!forceRefresh && now - lastFetchTime < CACHE_TTL && cachedEntries.length > 0) { 128 128 console.log('Returning cached entries'); 129 129 return NextResponse.json({ entries: cachedEntries }); 130 + } 131 + 132 + // If force refresh is requested, clear the didResolutionCache to force fresh handle resolution 133 + if (forceRefresh) { 134 + console.log('Force refresh requested, clearing DID resolution cache'); 135 + didResolutionCache.clear(); 130 136 } 131 137 132 138 console.log('Fetching fresh entries'); ··· 136 142 const supabase = createClient(supabaseUrl, supabaseKey); 137 143 138 144 // Fetch entries from the flushing_records table 145 + console.log(`Querying database for latest ${MAX_ENTRIES} entries...`); 139 146 const { data: entries, error } = await supabase 140 147 .from('flushing_records') 141 148 .select(` ··· 152 159 153 160 if (error) { 154 161 throw new Error(`Supabase error: ${error.message}`); 162 + } 163 + 164 + console.log(`Retrieved ${entries?.length || 0} entries from database. Latest created_at: ${entries?.[0]?.created_at || 'none'}`); 165 + 166 + // If no entries were found, this is suspicious - log a warning 167 + if (!entries || entries.length === 0) { 168 + console.warn('No entries found in database - this may indicate a problem'); 155 169 } 156 170 157 171 // Transform the data to match our client-side model ··· 249 263 return mockEntries; 250 264 } 251 265 252 - // Function to attempt to resolve a DID to a handle 266 + // DID resolution cache 267 + const didResolutionCache = new Map<string, string>(); 268 + 269 + // Timeout promise to prevent hanging on API calls 270 + function timeout(ms: number): Promise<never> { 271 + return new Promise((_, reject) => { 272 + setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms); 273 + }); 274 + } 275 + 276 + // Function to attempt to resolve a DID to a handle with timeout 253 277 // First tries PLC directory, then tries Bluesky API as a fallback 254 278 async function resolveDidToHandle(did: string): Promise<string> { 255 279 try { ··· 258 282 `${did.substring(0, 13)}...` : 259 283 `${did.substring(0, 10)}...`; 260 284 285 + // Check if we have a cached result for this DID 286 + if (didResolutionCache.has(did)) { 287 + return didResolutionCache.get(did)!; 288 + } 289 + 261 290 // Try PLC directory first - most reliable and doesn't require auth 262 291 if (did && did.startsWith('did:plc:')) { 263 292 try { 264 - console.log(`Trying PLC directory for DID: ${did}`); 265 - const plcResponse = await fetch(`https://plc.directory/${did}`); 266 - 267 - if (plcResponse.ok) { 268 - const plcData = await plcResponse.json(); 269 - if (plcData && plcData.alsoKnownAs && plcData.alsoKnownAs.length > 0) { 270 - // alsoKnownAs contains values like 'at://user.bsky.social' 271 - for (const aka of plcData.alsoKnownAs) { 272 - if (aka.startsWith('at://')) { 273 - const handle = aka.split('//')[1]; 274 - if (handle) { 275 - console.log(`Resolved ${did} to handle ${handle} via PLC directory`); 276 - return handle; 293 + // Use timeout to prevent hanging 294 + const plcResolver = async () => { 295 + console.log(`Trying PLC directory for DID: ${did}`); 296 + 297 + try { 298 + // Try main PLC endpoint 299 + const controller = new AbortController(); 300 + const timeoutId = setTimeout(() => controller.abort(), 3000); 301 + const plcResponse = await fetch(`https://plc.directory/${did}`, { 302 + signal: controller.signal 303 + }); 304 + clearTimeout(timeoutId); 305 + 306 + if (plcResponse.ok) { 307 + const plcData = await plcResponse.json(); 308 + if (plcData && plcData.alsoKnownAs && plcData.alsoKnownAs.length > 0) { 309 + // alsoKnownAs contains values like 'at://user.bsky.social' 310 + for (const aka of plcData.alsoKnownAs) { 311 + if (aka.startsWith('at://')) { 312 + const handle = aka.split('//')[1]; 313 + if (handle) { 314 + console.log(`Resolved ${did} to handle ${handle} via PLC directory`); 315 + didResolutionCache.set(did, handle); 316 + return handle; 317 + } 318 + } 277 319 } 278 320 } 279 321 } 322 + } catch (err) { 323 + console.warn(`Error with main PLC endpoint for ${did}:`, err); 280 324 } 281 - } 282 - 283 - // Try alternate PLC directory endpoint format 284 - const altPlcResponse = await fetch(`https://plc.directory/${did}/data`); 285 - if (altPlcResponse.ok) { 286 - const altPlcData = await altPlcResponse.json(); 287 - if (altPlcData && altPlcData.alsoKnownAs && altPlcData.alsoKnownAs.length > 0) { 288 - for (const aka of altPlcData.alsoKnownAs) { 289 - if (aka.startsWith('at://')) { 290 - const handle = aka.split('//')[1]; 291 - if (handle) { 292 - console.log(`Resolved ${did} to handle ${handle} via PLC directory (alternate endpoint)`); 293 - return handle; 325 + 326 + try { 327 + // Try alternate PLC endpoint 328 + const altController = new AbortController(); 329 + const altTimeoutId = setTimeout(() => altController.abort(), 3000); 330 + const altPlcResponse = await fetch(`https://plc.directory/${did}/data`, { 331 + signal: altController.signal 332 + }); 333 + clearTimeout(altTimeoutId); 334 + 335 + if (altPlcResponse.ok) { 336 + const altPlcData = await altPlcResponse.json(); 337 + if (altPlcData && altPlcData.alsoKnownAs && altPlcData.alsoKnownAs.length > 0) { 338 + for (const aka of altPlcData.alsoKnownAs) { 339 + if (aka.startsWith('at://')) { 340 + const handle = aka.split('//')[1]; 341 + if (handle) { 342 + console.log(`Resolved ${did} to handle ${handle} via PLC directory (alternate endpoint)`); 343 + didResolutionCache.set(did, handle); 344 + return handle; 345 + } 346 + } 294 347 } 295 348 } 296 349 } 350 + } catch (err) { 351 + console.warn(`Error with alternate PLC endpoint for ${did}:`, err); 297 352 } 298 - } 353 + 354 + throw new Error('PLC resolution failed'); 355 + }; 356 + 357 + // Try PLC with a timeout 358 + await Promise.race([ 359 + plcResolver(), 360 + timeout(5000) // 5 second overall timeout 361 + ]); 299 362 } catch (plcError) { 300 363 console.warn(`Failed to resolve handle from PLC directory for DID ${did}:`, plcError); 301 364 // Continue to next method ··· 303 366 } 304 367 305 368 // Fall back to Bluesky API 306 - console.log(`Falling back to Bluesky API for DID: ${did}`); 307 369 try { 308 - // Try to resolve DID directly with Bluesky API 309 - await agent.login({ identifier: '', password: '' }); 310 - const response = await agent.getProfile({ actor: did }); 311 - if (response?.data?.handle) { 312 - console.log(`Resolved ${did} to handle ${response.data.handle} via Bluesky API`); 313 - return response.data.handle; 314 - } 370 + console.log(`Falling back to Bluesky API for DID: ${did}`); 371 + 372 + const bskyResolver = async () => { 373 + // Try to resolve DID directly with Bluesky API 374 + await agent.login({ identifier: '', password: '' }); 375 + const response = await agent.getProfile({ actor: did }); 376 + if (response?.data?.handle) { 377 + console.log(`Resolved ${did} to handle ${response.data.handle} via Bluesky API`); 378 + didResolutionCache.set(did, response.data.handle); 379 + return response.data.handle; 380 + } 381 + throw new Error('Bluesky API resolution failed'); 382 + }; 383 + 384 + // Try Bluesky API with a timeout 385 + await Promise.race([ 386 + bskyResolver(), 387 + timeout(5000) // 5 second timeout 388 + ]); 315 389 } catch (apiError) { 316 390 console.error(`Failed to resolve handle with Bluesky API for DID ${did}:`, apiError); 317 391 } 318 392 319 393 // If we get here, all resolution methods failed 320 394 console.log(`All resolution methods failed for ${did}, using shortened DID: ${shortDid}`); 395 + // Cache the fallback result too 396 + didResolutionCache.set(did, shortDid); 321 397 return shortDid; 322 398 } catch (error) { 323 399 console.error(`Failed to resolve handle for DID ${did}:`, error);
+10 -1
app/src/app/page.tsx
··· 40 40 41 41 useEffect(() => { 42 42 // Fetch the latest entries when the component mounts 43 - fetchLatestEntries(); 43 + fetchLatestEntries(true); // Force refresh on initial load 44 + 45 + // Set up periodic refresh every 60 seconds 46 + const refreshInterval = setInterval(() => { 47 + console.log('Auto-refreshing feed...'); 48 + fetchLatestEntries(true); 49 + }, 60000); 50 + 51 + // Clean up interval on unmount 52 + return () => clearInterval(refreshInterval); 44 53 }, []); 45 54 46 55 // Toggle status update form