decentralised sync engine
1import { channelSessions } from "@/lib/state"; 2import type { AtUri, Did } from "@/lib/types/atproto"; 3import { systemsGmstnDevelopmentChannelRecordSchema } from "@/lib/types/lexicon/systems.gmstn.development.channel"; 4import { 5 atUriToString, 6 getRecordFromAtUri, 7 stringToAtUri, 8} from "@/lib/utils/atproto"; 9import { getConstellationBacklink } from "@/lib/utils/constellation"; 10import { isDomain } from "@/lib/utils/domains"; 11import { initiateHandshakeTo } from "@/lib/utils/handshake"; 12 13export const performHandshakes = async (latticeAtUri: AtUri) => { 14 const latticeAtUriString = atUriToString(latticeAtUri); 15 const constellationBacklinksResult = await getConstellationBacklink({ 16 subject: latticeAtUriString, 17 source: { 18 nsid: "systems.gmstn.development.channel", 19 fieldName: "routeThrough.uri", 20 }, 21 }); 22 23 if (!constellationBacklinksResult.ok) { 24 throw new Error( 25 "Something went wrong fetching constellation backlinks to do Shard handshakes", 26 ); 27 } 28 29 const { records: channelBacklinks } = constellationBacklinksResult.data; 30 31 // TODO: For private lattices, do permission check on owner's PDS 32 // and filter out records from unauthorised pdses. 33 34 const channelRecordsPromises = channelBacklinks.map( 35 async ({ did, collection, rkey }) => 36 await getRecordFromAtUri({ 37 // @ts-expect-error seriously i gotta do something about the template literals not converting properly SIGH 38 authority: did, 39 collection, 40 rKey: rkey, 41 }), 42 ); 43 44 const channelRecordResults = await Promise.all(channelRecordsPromises); 45 46 // mapping of shard -> list of channels (all AtUris) 47 const channelsByShard = new Map<AtUri, Array<AtUri>>(); 48 49 channelRecordResults.forEach((result, idx) => { 50 if (!result.ok) return; 51 const { success, data: channelRecord } = 52 systemsGmstnDevelopmentChannelRecordSchema.safeParse(result.data); 53 if (!success) return; 54 const { storeAt } = channelRecord; 55 56 const storeAtAtUriResult = stringToAtUri(storeAt.uri); 57 if (!storeAtAtUriResult.ok) return; 58 const storeAtAtUri = storeAtAtUriResult.data; 59 60 // this is fine because Promise.all() preserves the order of the arrays 61 const { 62 did: authority, 63 collection, 64 rkey: rKey, 65 } = channelBacklinks[idx]; 66 67 const existingMapValue = channelsByShard.get(storeAtAtUri); 68 69 const currentChannelUri: Required<AtUri> = { 70 // @ts-expect-error seriously i gotta do something about the template literals not converting properly SIGH 71 authority, 72 collection, 73 rKey, 74 }; 75 76 if (!existingMapValue) { 77 channelsByShard.set(storeAtAtUri, [currentChannelUri]); 78 } else { 79 const prevUris = existingMapValue; 80 channelsByShard.set(storeAtAtUri, [...prevUris, currentChannelUri]); 81 } 82 }); 83 84 const channelsByShardEntries = channelsByShard.entries(); 85 86 for (const entry of channelsByShardEntries) { 87 const shardAtUri = entry[0]; 88 89 let shardDid: Did | undefined; 90 // TODO: if the rkey of the shard URI is not a valid domain, then it must be a did:plc 91 // we need to find a better way to enforce this. we really should explore just resolving the 92 // record and then checking the record value for the actual domain instead. 93 // did resolution hard;; 94 if ( 95 isDomain(shardAtUri.rKey ?? "") || 96 shardAtUri.rKey?.startsWith("localhost:") 97 ) { 98 // from the isDomain check, if we pass, we can conclude that 99 shardDid = `did:web:${encodeURIComponent(shardAtUri.rKey ?? "")}`; 100 } else { 101 shardDid = `did:plc:${encodeURIComponent(shardAtUri.rKey ?? "")}`; 102 } 103 104 const channelAtUris = entry[1]; 105 106 const handshakeResult = await initiateHandshakeTo({ 107 did: shardDid, 108 channels: channelAtUris, 109 }); 110 if (!handshakeResult.ok) continue; 111 const sessionInfo = handshakeResult.data; 112 console.log("Handshake to", shardAtUri.rKey, "complete!"); 113 channelSessions.set(shardAtUri, sessionInfo); 114 } 115};