tangled
alpha
login
or
join now
nekomimi.pet
/
cutebook
28
fork
atom
web components for a integratabtle atproto based guestbook
28
fork
atom
overview
issues
pulls
pipelines
async fetch profiles
nekomimi.pet
3 months ago
7befa943
f4580322
verified
This commit was signed with the committer's
known signature
.
nekomimi.pet
SSH Key Fingerprint:
SHA256:knUvGhH8rbrdqbzO9WXAIaTK0LrdYw2UC0qWB43Ic0Q=
+75
-33
1 changed file
expand all
collapse all
unified
split
lib
guestbook-display.ts
+75
-33
lib/guestbook-display.ts
···
315
}
316
317
const data: ConstellationBacklinksResponse = await response.json();
318
-
319
console.log('Constellation response:', data);
320
321
// fetch actual records
···
323
console.warn('No records found in response');
324
this.signatures = [];
325
} else {
326
-
// fetch each record from the repository
327
-
const signatures: GuestbookSignature[] = [];
328
-
329
-
for (const record of data.records) {
330
try {
331
const recordUrl = new URL('/xrpc/com.atproto.repo.getRecord', 'https://slingshot.wisp.place');
332
recordUrl.searchParams.set('repo', record.did);
333
recordUrl.searchParams.set('collection', record.collection);
334
recordUrl.searchParams.set('rkey', record.rkey);
335
-
336
const recordResponse = await fetch(recordUrl.toString());
337
if (!recordResponse.ok) {
338
console.warn(`Failed to fetch record ${record.did}/${record.collection}/${record.rkey}`);
339
-
continue;
340
}
341
-
342
const recordData = await recordResponse.json();
343
-
344
// validate the record
345
if (
346
recordData.value &&
···
348
typeof recordData.value.message === 'string' &&
349
typeof recordData.value.createdAt === 'string'
350
) {
351
-
// Fetch the handle for this author
352
-
let authorHandle: string | undefined;
353
-
try {
354
-
const profileResponse = await fetch(
355
-
`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${record.did}`
356
-
);
357
-
if (profileResponse.ok) {
358
-
const profileData = await profileResponse.json();
359
-
authorHandle = profileData.handle;
360
-
}
361
-
} catch (err) {
362
-
console.warn(`Failed to fetch handle for ${record.did}:`, err);
363
-
}
364
-
365
-
signatures.push({
366
uri: recordData.uri,
367
cid: recordData.cid,
368
value: recordData.value,
369
author: record.did,
370
-
authorHandle: authorHandle,
371
-
});
372
}
373
} catch (err) {
374
console.warn(`Error fetching record ${record.did}/${record.collection}/${record.rkey}:`, err);
375
}
376
-
}
377
-
378
-
// sort by creation time, newest first
379
-
this.signatures = signatures.sort((a, b) => {
0
0
0
0
380
return new Date(b.value.createdAt).getTime() - new Date(a.value.createdAt).getTime();
381
});
382
-
}
383
0
384
this.loading = false;
385
this.updateContent();
386
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
387
} catch (error) {
388
console.error('Error fetching signatures:', error);
389
this.error = error instanceof Error ? error.message : 'Unknown error occurred';
···
403
}
404
405
private shortenDid(did: string): string {
406
-
if (did.startsWith('did:plc:')) {
407
-
return `${did.slice(0, 16)}...${did.slice(-4)}`;
0
0
0
408
}
409
return did;
410
}
···
315
}
316
317
const data: ConstellationBacklinksResponse = await response.json();
318
+
319
console.log('Constellation response:', data);
320
321
// fetch actual records
···
323
console.warn('No records found in response');
324
this.signatures = [];
325
} else {
326
+
// Fetch all records in parallel
327
+
const recordPromises = data.records.map(async (record) => {
0
0
328
try {
329
const recordUrl = new URL('/xrpc/com.atproto.repo.getRecord', 'https://slingshot.wisp.place');
330
recordUrl.searchParams.set('repo', record.did);
331
recordUrl.searchParams.set('collection', record.collection);
332
recordUrl.searchParams.set('rkey', record.rkey);
333
+
334
const recordResponse = await fetch(recordUrl.toString());
335
if (!recordResponse.ok) {
336
console.warn(`Failed to fetch record ${record.did}/${record.collection}/${record.rkey}`);
337
+
return null;
338
}
339
+
340
const recordData = await recordResponse.json();
341
+
342
// validate the record
343
if (
344
recordData.value &&
···
346
typeof recordData.value.message === 'string' &&
347
typeof recordData.value.createdAt === 'string'
348
) {
349
+
return {
0
0
0
0
0
0
0
0
0
0
0
0
0
0
350
uri: recordData.uri,
351
cid: recordData.cid,
352
value: recordData.value,
353
author: record.did,
354
+
authorHandle: undefined,
355
+
} as GuestbookSignature;
356
}
357
} catch (err) {
358
console.warn(`Error fetching record ${record.did}/${record.collection}/${record.rkey}:`, err);
359
}
360
+
return null;
361
+
});
362
+
363
+
const results = await Promise.all(recordPromises);
364
+
const validSignatures = results.filter((sig): sig is GuestbookSignature => sig !== null);
365
+
366
+
// Sort once after collecting all signatures
367
+
validSignatures.sort((a, b) => {
368
return new Date(b.value.createdAt).getTime() - new Date(a.value.createdAt).getTime();
369
});
0
370
371
+
this.signatures = validSignatures;
372
this.loading = false;
373
this.updateContent();
374
375
+
// Batch fetch profiles asynchronously
376
+
if (validSignatures.length > 0) {
377
+
const uniqueDids = Array.from(new Set(validSignatures.map(sig => sig.author)));
378
+
379
+
// Batch fetch profiles up to 25 at a time (API limit)
380
+
const profilePromises = [];
381
+
for (let i = 0; i < uniqueDids.length; i += 25) {
382
+
const batch = uniqueDids.slice(i, i + 25);
383
+
384
+
const profileUrl = new URL('/xrpc/app.bsky.actor.getProfiles', 'https://public.api.bsky.app');
385
+
batch.forEach(d => profileUrl.searchParams.append('actors', d));
386
+
387
+
profilePromises.push(
388
+
fetch(profileUrl.toString())
389
+
.then(profileResponse => profileResponse.ok ? profileResponse.json() : null)
390
+
.then(profilesData => {
391
+
if (profilesData?.profiles && Array.isArray(profilesData.profiles)) {
392
+
const handles = new Map<string, string>();
393
+
profilesData.profiles.forEach((profile: any) => {
394
+
if (profile.handle) {
395
+
handles.set(profile.did, profile.handle);
396
+
}
397
+
});
398
+
return handles;
399
+
}
400
+
return new Map<string, string>();
401
+
})
402
+
.catch((err) => {
403
+
console.warn('Failed to fetch profile batch:', err);
404
+
return new Map<string, string>();
405
+
})
406
+
);
407
+
}
408
+
409
+
// Wait for all profile batches, then update once
410
+
const handleMaps = await Promise.all(profilePromises);
411
+
const allHandles = new Map<string, string>();
412
+
handleMaps.forEach(map => {
413
+
map.forEach((handle, did) => allHandles.set(did, handle));
414
+
});
415
+
416
+
if (allHandles.size > 0) {
417
+
this.signatures = this.signatures.map(sig => {
418
+
const handle = allHandles.get(sig.author);
419
+
return handle ? { ...sig, authorHandle: handle } : sig;
420
+
});
421
+
this.updateContent();
422
+
}
423
+
}
424
+
}
425
+
426
} catch (error) {
427
console.error('Error fetching signatures:', error);
428
this.error = error instanceof Error ? error.message : 'Unknown error occurred';
···
442
}
443
444
private shortenDid(did: string): string {
445
+
if (did.startsWith('did:')) {
446
+
const afterPrefix = did.indexOf(':', 4);
447
+
if (afterPrefix !== -1) {
448
+
return `${did.slice(0, afterPrefix + 9)}...`;
449
+
}
450
}
451
return did;
452
}