import type { ComAtprotoServerDescribeServer } from '@atcute/atproto' import { Client, simpleFetchHandler } from '@atcute/client' export type BaseProvider = { name: string url: URL location: string subtitle?: string /** whether this provider should be prominently displayed as a login option */ default?: boolean /** whether this provider should be pinned when displayed */ pin?: boolean /** if the PDS has a policy on who can use what handles, blacksky, for example. */ handlePolicy?: URL } export type HydratedProvider = BaseProvider & { server: ComAtprotoServerDescribeServer.$output } export const PROVIDERS: BaseProvider[] = [ { name: 'selfhosted.social', url: new URL('https://selfhosted.social/'), subtitle: 'A popular community-run PDS', location: 'US', pin: true, }, { name: 'Blacksky', url: new URL('https://blacksky.app/'), subtitle: 'A PDS for the black community and allies.', location: 'US', handlePolicy: new URL( 'https://docs.blacksky.community/migrating-to-blacksky-pds-complete-guide#who-can-use-blacksky-services', ), default: true, }, { name: 'Bluesky', url: new URL('https://bsky.social/'), location: 'US', default: true, }, { name: 'Tophhie Social', url: new URL('https://pds.tophhie.cloud'), location: 'GB', }, ] export async function getHydratedProviders(): Promise { return ( await Promise.all( PROVIDERS.map(async (provider) => { const xrpc = new Client({ handler: simpleFetchHandler({ service: provider.url }), }) const { data, ok } = await xrpc.get('com.atproto.server.describeServer', {}) if (!ok) return undefined return { ...provider, server: data, } }), ) ).filter(Boolean) as HydratedProvider[] } function shuffle(array: T[]): T[] { const shuffled = [...array] for (let i = shuffled.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) const temp = shuffled[i]! shuffled[i] = shuffled[j]! shuffled[j] = temp } return shuffled } export async function getProviderList(shuffled = true): Promise { const hydratedProviders = await getHydratedProviders() const pinnedProviders = hydratedProviders.filter((provider) => provider.pin) const unpinnedProviders = hydratedProviders.filter((provider) => !provider.pin) if (shuffled) return [...pinnedProviders, ...shuffle(unpinnedProviders)] return [...pinnedProviders, ...unpinnedProviders] }