···
34
34
const MAX_ENTRIES = 20;
35
35
36
36
// Cache settings to avoid hitting the database too frequently
37
37
-
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes in milliseconds
37
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
126
-
// For normal (non-pagination) requests, use the cache if valid
126
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
130
+
}
131
131
+
132
132
+
// If force refresh is requested, clear the didResolutionCache to force fresh handle resolution
133
133
+
if (forceRefresh) {
134
134
+
console.log('Force refresh requested, clearing DID resolution cache');
135
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
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
162
+
}
163
163
+
164
164
+
console.log(`Retrieved ${entries?.length || 0} entries from database. Latest created_at: ${entries?.[0]?.created_at || 'none'}`);
165
165
+
166
166
+
// If no entries were found, this is suspicious - log a warning
167
167
+
if (!entries || entries.length === 0) {
168
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
252
-
// Function to attempt to resolve a DID to a handle
266
266
+
// DID resolution cache
267
267
+
const didResolutionCache = new Map<string, string>();
268
268
+
269
269
+
// Timeout promise to prevent hanging on API calls
270
270
+
function timeout(ms: number): Promise<never> {
271
271
+
return new Promise((_, reject) => {
272
272
+
setTimeout(() => reject(new Error(`Operation timed out after ${ms}ms`)), ms);
273
273
+
});
274
274
+
}
275
275
+
276
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
285
+
// Check if we have a cached result for this DID
286
286
+
if (didResolutionCache.has(did)) {
287
287
+
return didResolutionCache.get(did)!;
288
288
+
}
289
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
264
-
console.log(`Trying PLC directory for DID: ${did}`);
265
265
-
const plcResponse = await fetch(`https://plc.directory/${did}`);
266
266
-
267
267
-
if (plcResponse.ok) {
268
268
-
const plcData = await plcResponse.json();
269
269
-
if (plcData && plcData.alsoKnownAs && plcData.alsoKnownAs.length > 0) {
270
270
-
// alsoKnownAs contains values like 'at://user.bsky.social'
271
271
-
for (const aka of plcData.alsoKnownAs) {
272
272
-
if (aka.startsWith('at://')) {
273
273
-
const handle = aka.split('//')[1];
274
274
-
if (handle) {
275
275
-
console.log(`Resolved ${did} to handle ${handle} via PLC directory`);
276
276
-
return handle;
293
293
+
// Use timeout to prevent hanging
294
294
+
const plcResolver = async () => {
295
295
+
console.log(`Trying PLC directory for DID: ${did}`);
296
296
+
297
297
+
try {
298
298
+
// Try main PLC endpoint
299
299
+
const controller = new AbortController();
300
300
+
const timeoutId = setTimeout(() => controller.abort(), 3000);
301
301
+
const plcResponse = await fetch(`https://plc.directory/${did}`, {
302
302
+
signal: controller.signal
303
303
+
});
304
304
+
clearTimeout(timeoutId);
305
305
+
306
306
+
if (plcResponse.ok) {
307
307
+
const plcData = await plcResponse.json();
308
308
+
if (plcData && plcData.alsoKnownAs && plcData.alsoKnownAs.length > 0) {
309
309
+
// alsoKnownAs contains values like 'at://user.bsky.social'
310
310
+
for (const aka of plcData.alsoKnownAs) {
311
311
+
if (aka.startsWith('at://')) {
312
312
+
const handle = aka.split('//')[1];
313
313
+
if (handle) {
314
314
+
console.log(`Resolved ${did} to handle ${handle} via PLC directory`);
315
315
+
didResolutionCache.set(did, handle);
316
316
+
return handle;
317
317
+
}
318
318
+
}
277
319
}
278
320
}
279
321
}
322
322
+
} catch (err) {
323
323
+
console.warn(`Error with main PLC endpoint for ${did}:`, err);
280
324
}
281
281
-
}
282
282
-
283
283
-
// Try alternate PLC directory endpoint format
284
284
-
const altPlcResponse = await fetch(`https://plc.directory/${did}/data`);
285
285
-
if (altPlcResponse.ok) {
286
286
-
const altPlcData = await altPlcResponse.json();
287
287
-
if (altPlcData && altPlcData.alsoKnownAs && altPlcData.alsoKnownAs.length > 0) {
288
288
-
for (const aka of altPlcData.alsoKnownAs) {
289
289
-
if (aka.startsWith('at://')) {
290
290
-
const handle = aka.split('//')[1];
291
291
-
if (handle) {
292
292
-
console.log(`Resolved ${did} to handle ${handle} via PLC directory (alternate endpoint)`);
293
293
-
return handle;
325
325
+
326
326
+
try {
327
327
+
// Try alternate PLC endpoint
328
328
+
const altController = new AbortController();
329
329
+
const altTimeoutId = setTimeout(() => altController.abort(), 3000);
330
330
+
const altPlcResponse = await fetch(`https://plc.directory/${did}/data`, {
331
331
+
signal: altController.signal
332
332
+
});
333
333
+
clearTimeout(altTimeoutId);
334
334
+
335
335
+
if (altPlcResponse.ok) {
336
336
+
const altPlcData = await altPlcResponse.json();
337
337
+
if (altPlcData && altPlcData.alsoKnownAs && altPlcData.alsoKnownAs.length > 0) {
338
338
+
for (const aka of altPlcData.alsoKnownAs) {
339
339
+
if (aka.startsWith('at://')) {
340
340
+
const handle = aka.split('//')[1];
341
341
+
if (handle) {
342
342
+
console.log(`Resolved ${did} to handle ${handle} via PLC directory (alternate endpoint)`);
343
343
+
didResolutionCache.set(did, handle);
344
344
+
return handle;
345
345
+
}
346
346
+
}
294
347
}
295
348
}
296
349
}
350
350
+
} catch (err) {
351
351
+
console.warn(`Error with alternate PLC endpoint for ${did}:`, err);
297
352
}
298
298
-
}
353
353
+
354
354
+
throw new Error('PLC resolution failed');
355
355
+
};
356
356
+
357
357
+
// Try PLC with a timeout
358
358
+
await Promise.race([
359
359
+
plcResolver(),
360
360
+
timeout(5000) // 5 second overall timeout
361
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
306
-
console.log(`Falling back to Bluesky API for DID: ${did}`);
307
369
try {
308
308
-
// Try to resolve DID directly with Bluesky API
309
309
-
await agent.login({ identifier: '', password: '' });
310
310
-
const response = await agent.getProfile({ actor: did });
311
311
-
if (response?.data?.handle) {
312
312
-
console.log(`Resolved ${did} to handle ${response.data.handle} via Bluesky API`);
313
313
-
return response.data.handle;
314
314
-
}
370
370
+
console.log(`Falling back to Bluesky API for DID: ${did}`);
371
371
+
372
372
+
const bskyResolver = async () => {
373
373
+
// Try to resolve DID directly with Bluesky API
374
374
+
await agent.login({ identifier: '', password: '' });
375
375
+
const response = await agent.getProfile({ actor: did });
376
376
+
if (response?.data?.handle) {
377
377
+
console.log(`Resolved ${did} to handle ${response.data.handle} via Bluesky API`);
378
378
+
didResolutionCache.set(did, response.data.handle);
379
379
+
return response.data.handle;
380
380
+
}
381
381
+
throw new Error('Bluesky API resolution failed');
382
382
+
};
383
383
+
384
384
+
// Try Bluesky API with a timeout
385
385
+
await Promise.race([
386
386
+
bskyResolver(),
387
387
+
timeout(5000) // 5 second timeout
388
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
395
+
// Cache the fallback result too
396
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);
···
40
40
41
41
useEffect(() => {
42
42
// Fetch the latest entries when the component mounts
43
43
-
fetchLatestEntries();
43
43
+
fetchLatestEntries(true); // Force refresh on initial load
44
44
+
45
45
+
// Set up periodic refresh every 60 seconds
46
46
+
const refreshInterval = setInterval(() => {
47
47
+
console.log('Auto-refreshing feed...');
48
48
+
fetchLatestEntries(true);
49
49
+
}, 60000);
50
50
+
51
51
+
// Clean up interval on unmount
52
52
+
return () => clearInterval(refreshInterval);
44
53
}, []);
45
54
46
55
// Toggle status update form