Client side atproto account migrator in your web browser, along with services for backups and adversarial migrations.
pdsmoover.com
pds
atproto
migrations
moo
cow
1import {
2 CompositeDidDocumentResolver,
3 CompositeHandleResolver,
4 DohJsonHandleResolver,
5 PlcDidDocumentResolver,
6 WebDidDocumentResolver,
7 WellKnownHandleResolver,
8} from '@atcute/identity-resolver'
9
10const handleResolver = new CompositeHandleResolver({
11 strategy: 'race',
12 methods: {
13 dns: new DohJsonHandleResolver({
14 dohUrl: 'https://mozilla.cloudflare-dns.com/dns-query',
15 }),
16 http: new WellKnownHandleResolver(),
17 },
18})
19
20const docResolver = new CompositeDidDocumentResolver({
21 methods: {
22 plc: new PlcDidDocumentResolver(),
23 web: new WebDidDocumentResolver(),
24 },
25})
26
27/**
28 * Fetches a minidoc from slingshot
29 *
30 * @param identifier {string}
31 * @returns {Promise<{did: string, handle: string, pds: string}>}
32 */
33async function getMiniDoc(identifier) {
34 const result = await fetch(
35 `https://slingshot.microcosm.blue/xrpc/blue.microcosm.identity.resolveMiniDoc?identifier=${identifier}`,
36 )
37 if (!result.ok) {
38 throw new Error(`Failed to fetch minidoc: ${result.status} ${result.statusText}`)
39 }
40 return await result.json()
41}
42
43/**
44 * Cleans the handle of @ and some other unicode characters that used to show up when copied from the profile
45 * @param handle {string}
46 * @returns {string}
47 */
48const cleanHandle = handle =>
49 handle
50 .replace('@', '')
51 .trim()
52 .replace(/[\u202A\u202C\u200E\u200F\u2066-\u2069]/g, '')
53
54/**
55 * Convince helper to resolve a handle to a did and then find the PDS url from the did document.
56 *
57 * @param handle
58 * @returns {Promise<{usersDid: string, pds: string}>}
59 */
60async function handleAndPDSResolver(handle) {
61 try {
62 const { did, handle: _, pds } = await getMiniDoc(handle)
63 return { usersDid: did, pds }
64 } catch (error) {
65 console.error('Failed to load mini doc, trying other routes', error)
66 }
67
68 let usersDid = null
69 if (handle.startsWith('did:')) {
70 usersDid = handle
71 } else {
72 const cleanedHandle = cleanHandle(handle)
73 usersDid = await handleResolver.resolve(cleanedHandle)
74 }
75 const didDoc = await docResolver.resolve(usersDid)
76
77 let pds
78 try {
79 pds = didDoc.service?.filter(s => s.type === 'AtprotoPersonalDataServer')[0].serviceEndpoint
80 } catch (error) {
81 throw new Error('Could not find a PDS in the DID document.')
82 }
83 return { usersDid, pds }
84}
85
86/**
87 * Fetches the DID Web from the .well-known/did.json endpoint of the server.
88 * Legacy and was helpful if the web ui and server are on the same domain, not as useful now
89 * @param baseUrl
90 * @returns {Promise<*>}
91 */
92async function fetchPDSMooverDIDWeb(baseUrl) {
93 const response = await fetch(`${baseUrl}/.well-known/did.json`)
94 if (!response.ok) {
95 throw new Error(`Failed to fetch DID document: ${response.status}`)
96 }
97 const didDoc = await response.json()
98 return didDoc.id
99}
100
101export { handleResolver, docResolver, cleanHandle, handleAndPDSResolver, fetchPDSMooverDIDWeb }