|
1 | | -export interface AtprotoProfile { |
2 | | - did: string |
3 | | - handle: string |
4 | | - displayName?: string |
5 | | - avatar?: string |
6 | | -} |
| 1 | +import { |
| 2 | + ONE_THOUSAND_NPMX_USER_ACCOUNTS_XRPC, |
| 3 | + BSKY_APP_VIEW_USER_PROFILES_XRPC, |
| 4 | + ERROR_PDS_FETCH_FAILED, |
| 5 | +} from '#shared/utils/constants' |
| 6 | +import type { AtprotoProfile } from '#shared/types/atproto' |
| 7 | + |
| 8 | +const USER_BATCH_AMOUNT = 25 |
7 | 9 |
|
8 | 10 | export default defineCachedEventHandler( |
9 | 11 | async (): Promise<AtprotoProfile[]> => { |
10 | | - const response = await fetch('https://npmx.social/xrpc/com.atproto.sync.listRepos?limit=1000') |
| 12 | + // INFO: Request npmx.social PDS for every hosted user account |
| 13 | + const response = await fetch(ONE_THOUSAND_NPMX_USER_ACCOUNTS_XRPC) |
11 | 14 |
|
12 | 15 | if (!response.ok) { |
13 | 16 | throw createError({ |
14 | 17 | statusCode: response.status, |
15 | | - message: 'Failed to fetch PDS repos', |
| 18 | + message: ERROR_PDS_FETCH_FAILED, |
16 | 19 | }) |
17 | 20 | } |
18 | 21 |
|
19 | 22 | const listRepos = (await response.json()) as { repos: { did: string }[] } |
20 | 23 | const dids = listRepos.repos.map(repo => repo.did) |
21 | 24 |
|
22 | | - const getProfilesUrl = 'https://public.api.bsky.app/xrpc/app.bsky.actor.getProfiles' |
23 | | - const allProfiles: AtprotoProfile[] = [] |
24 | | - |
25 | | - for (let i = 0; i < dids.length; i += 25) { |
26 | | - const batch = dids.slice(i, i + 25) |
27 | | - |
28 | | - const params = new URLSearchParams() |
29 | | - for (const did of batch) { |
30 | | - params.append('actors', did) |
31 | | - } |
32 | | - |
33 | | - try { |
34 | | - const profilesResponse = await fetch(`${getProfilesUrl}?${params.toString()}`) |
35 | | - |
36 | | - if (!profilesResponse.ok) { |
37 | | - console.warn(`Failed to fetch atproto profiles: ${profilesResponse.status}`) |
38 | | - continue |
39 | | - } |
40 | | - |
41 | | - const profilesData = (await profilesResponse.json()) as { profiles: AtprotoProfile[] } |
42 | | - |
43 | | - if (profilesData.profiles) { |
44 | | - allProfiles.push(...profilesData.profiles) |
45 | | - } |
46 | | - } catch (error) { |
47 | | - console.warn('Failed to fetch atproto profiles:', error) |
48 | | - } |
| 25 | + // INFO: Request the list of user profiles from the Bluesky AppView |
| 26 | + const batchPromises: Promise<AtprotoProfile[]>[] = [] |
| 27 | + |
| 28 | + for (let i = 0; i < dids.length; i += USER_BATCH_AMOUNT) { |
| 29 | + const batch = dids.slice(i, i + USER_BATCH_AMOUNT) |
| 30 | + const url = new URL(BSKY_APP_VIEW_USER_PROFILES_XRPC) |
| 31 | + |
| 32 | + for (const did of batch) url.searchParams.append('actors', did) |
| 33 | + |
| 34 | + batchPromises.push( |
| 35 | + fetch(url.toString()) |
| 36 | + .then(res => { |
| 37 | + if (!res.ok) throw new Error(`Status ${res.status}`) |
| 38 | + return res.json() as Promise<{ profiles: AtprotoProfile[] }> |
| 39 | + }) |
| 40 | + .then(data => data.profiles || []) |
| 41 | + .catch(err => { |
| 42 | + console.warn('Failed to fetch batch:', err) |
| 43 | + // Return empty array on failure so Promise.all doesn't crash |
| 44 | + return [] |
| 45 | + }), |
| 46 | + ) |
49 | 47 | } |
50 | 48 |
|
51 | | - return allProfiles |
| 49 | + // INFO: Await all batches in parallel and flatten the results |
| 50 | + return (await Promise.all(batchPromises)).flat() |
52 | 51 | }, |
53 | 52 | { |
54 | 53 | maxAge: 3600, |
|
0 commit comments