web components for a integrable atproto based guestbook

async fetch profiles

nekomimi.pet 7befa943 f4580322

verified
Changed files
+75 -33
lib
+75 -33
lib/guestbook-display.ts
···
}
const data: ConstellationBacklinksResponse = await response.json();
-
+
console.log('Constellation response:', data);
// fetch actual records
···
console.warn('No records found in response');
this.signatures = [];
} else {
-
// fetch each record from the repository
-
const signatures: GuestbookSignature[] = [];
-
-
for (const record of data.records) {
+
// Fetch all records in parallel
+
const recordPromises = data.records.map(async (record) => {
try {
const recordUrl = new URL('/xrpc/com.atproto.repo.getRecord', 'https://slingshot.wisp.place');
recordUrl.searchParams.set('repo', record.did);
recordUrl.searchParams.set('collection', record.collection);
recordUrl.searchParams.set('rkey', record.rkey);
-
+
const recordResponse = await fetch(recordUrl.toString());
if (!recordResponse.ok) {
console.warn(`Failed to fetch record ${record.did}/${record.collection}/${record.rkey}`);
-
continue;
+
return null;
}
-
+
const recordData = await recordResponse.json();
-
+
// validate the record
if (
recordData.value &&
···
typeof recordData.value.message === 'string' &&
typeof recordData.value.createdAt === 'string'
) {
-
// Fetch the handle for this author
-
let authorHandle: string | undefined;
-
try {
-
const profileResponse = await fetch(
-
`https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=${record.did}`
-
);
-
if (profileResponse.ok) {
-
const profileData = await profileResponse.json();
-
authorHandle = profileData.handle;
-
}
-
} catch (err) {
-
console.warn(`Failed to fetch handle for ${record.did}:`, err);
-
}
-
-
signatures.push({
+
return {
uri: recordData.uri,
cid: recordData.cid,
value: recordData.value,
author: record.did,
-
authorHandle: authorHandle,
-
});
+
authorHandle: undefined,
+
} as GuestbookSignature;
}
} catch (err) {
console.warn(`Error fetching record ${record.did}/${record.collection}/${record.rkey}:`, err);
}
-
}
-
-
// sort by creation time, newest first
-
this.signatures = signatures.sort((a, b) => {
+
return null;
+
});
+
+
const results = await Promise.all(recordPromises);
+
const validSignatures = results.filter((sig): sig is GuestbookSignature => sig !== null);
+
+
// Sort once after collecting all signatures
+
validSignatures.sort((a, b) => {
return new Date(b.value.createdAt).getTime() - new Date(a.value.createdAt).getTime();
});
-
}
+
this.signatures = validSignatures;
this.loading = false;
this.updateContent();
+
// Batch fetch profiles asynchronously
+
if (validSignatures.length > 0) {
+
const uniqueDids = Array.from(new Set(validSignatures.map(sig => sig.author)));
+
+
// Batch fetch profiles up to 25 at a time (API limit)
+
const profilePromises = [];
+
for (let i = 0; i < uniqueDids.length; i += 25) {
+
const batch = uniqueDids.slice(i, i + 25);
+
+
const profileUrl = new URL('/xrpc/app.bsky.actor.getProfiles', 'https://public.api.bsky.app');
+
batch.forEach(d => profileUrl.searchParams.append('actors', d));
+
+
profilePromises.push(
+
fetch(profileUrl.toString())
+
.then(profileResponse => profileResponse.ok ? profileResponse.json() : null)
+
.then(profilesData => {
+
if (profilesData?.profiles && Array.isArray(profilesData.profiles)) {
+
const handles = new Map<string, string>();
+
profilesData.profiles.forEach((profile: any) => {
+
if (profile.handle) {
+
handles.set(profile.did, profile.handle);
+
}
+
});
+
return handles;
+
}
+
return new Map<string, string>();
+
})
+
.catch((err) => {
+
console.warn('Failed to fetch profile batch:', err);
+
return new Map<string, string>();
+
})
+
);
+
}
+
+
// Wait for all profile batches, then update once
+
const handleMaps = await Promise.all(profilePromises);
+
const allHandles = new Map<string, string>();
+
handleMaps.forEach(map => {
+
map.forEach((handle, did) => allHandles.set(did, handle));
+
});
+
+
if (allHandles.size > 0) {
+
this.signatures = this.signatures.map(sig => {
+
const handle = allHandles.get(sig.author);
+
return handle ? { ...sig, authorHandle: handle } : sig;
+
});
+
this.updateContent();
+
}
+
}
+
}
+
} catch (error) {
console.error('Error fetching signatures:', error);
this.error = error instanceof Error ? error.message : 'Unknown error occurred';
···
}
private shortenDid(did: string): string {
-
if (did.startsWith('did:plc:')) {
-
return `${did.slice(0, 16)}...${did.slice(-4)}`;
+
if (did.startsWith('did:')) {
+
const afterPrefix = did.indexOf(':', 4);
+
if (afterPrefix !== -1) {
+
return `${did.slice(0, afterPrefix + 9)}...`;
+
}
}
return did;
}