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};