forked from
vt3e.cat/bbell
wip bsky client for the web & android
1import type { ComAtprotoServerDescribeServer } from '@atcute/atproto'
2import { Client, simpleFetchHandler } from '@atcute/client'
3
4export type BaseProvider = {
5 name: string
6 url: URL
7 location: string
8 subtitle?: string
9 /** whether this provider should be prominently displayed as a login option */
10 default?: boolean
11 /** whether this provider should be pinned when displayed */
12 pin?: boolean
13 /** if the PDS has a policy on who can use what handles, blacksky, for example. */
14 handlePolicy?: URL
15}
16
17export type HydratedProvider = BaseProvider & {
18 server: ComAtprotoServerDescribeServer.$output
19}
20
21export const PROVIDERS: BaseProvider[] = [
22 {
23 name: 'selfhosted.social',
24 url: new URL('https://selfhosted.social/'),
25 subtitle: 'A popular community-run PDS',
26 location: 'US',
27 pin: true,
28 },
29 {
30 name: 'Blacksky',
31 url: new URL('https://blacksky.app/'),
32 subtitle: 'A PDS for the black community and allies.',
33 location: 'US',
34 handlePolicy: new URL(
35 'https://docs.blacksky.community/migrating-to-blacksky-pds-complete-guide#who-can-use-blacksky-services',
36 ),
37 default: true,
38 },
39 {
40 name: 'Bluesky',
41 url: new URL('https://bsky.social/'),
42 location: 'US',
43 default: true,
44 },
45 {
46 name: 'Tophhie Social',
47 url: new URL('https://pds.tophhie.cloud'),
48 location: 'GB',
49 },
50]
51
52export async function getHydratedProviders(): Promise<HydratedProvider[]> {
53 return (
54 await Promise.all(
55 PROVIDERS.map(async (provider) => {
56 const xrpc = new Client({
57 handler: simpleFetchHandler({ service: provider.url }),
58 })
59
60 const { data, ok } = await xrpc.get('com.atproto.server.describeServer', {})
61 if (!ok) return undefined
62
63 return {
64 ...provider,
65 server: data,
66 }
67 }),
68 )
69 ).filter(Boolean) as HydratedProvider[]
70}
71
72function shuffle<T>(array: T[]): T[] {
73 const shuffled = [...array]
74 for (let i = shuffled.length - 1; i > 0; i--) {
75 const j = Math.floor(Math.random() * (i + 1))
76 const temp = shuffled[i]!
77 shuffled[i] = shuffled[j]!
78 shuffled[j] = temp
79 }
80
81 return shuffled
82}
83
84export async function getProviderList(shuffled = true): Promise<HydratedProvider[]> {
85 const hydratedProviders = await getHydratedProviders()
86 const pinnedProviders = hydratedProviders.filter((provider) => provider.pin)
87 const unpinnedProviders = hydratedProviders.filter((provider) => !provider.pin)
88
89 if (shuffled) return [...pinnedProviders, ...shuffle(unpinnedProviders)]
90 return [...pinnedProviders, ...unpinnedProviders]
91}