an appview-less Bluesky client using Constellation and PDS Queries reddwarf.app
frontend spa bluesky reddwarf microcosm
at main 4.1 kB view raw
1import { type Agent,AtUri } from "@atproto/api"; 2import { TID } from "@atproto/common-web"; 3import type { QueryClient } from "@tanstack/react-query"; 4 5import { type linksRecordsResponse,useQueryConstellation } from "./useQuery"; 6 7export function useGetFollowState({ 8 target, 9 user, 10}: { 11 target: string; 12 user?: string; 13}): string[] | undefined { 14 const { data: followData } = useQueryConstellation( 15 user 16 ? { 17 method: "/links", 18 target: target, 19 // @ts-expect-error overloading sucks so much 20 collection: "app.bsky.graph.follow", 21 path: ".subject", 22 dids: [user], 23 } 24 : { method: "undefined", target: "whatever" } 25 // overloading sucks so much 26 ) as { data: linksRecordsResponse | undefined }; 27 const follows = followData?.linking_records.slice(0, 50) ?? []; 28 29 if (follows.length > 0) { 30 return follows.map((linksRecord) => { 31 return `at://${linksRecord.did}/${linksRecord.collection}/${linksRecord.rkey}`; 32 }); 33 } 34 35 return undefined; 36} 37 38export function toggleFollow({ 39 agent, 40 targetDid, 41 followRecords, 42 queryClient, 43}: { 44 agent?: Agent; 45 targetDid?: string; 46 followRecords: undefined | string[]; 47 queryClient: QueryClient; 48}) { 49 if (!agent?.did || !targetDid) return; 50 51 const queryKey = [ 52 "constellation", 53 "/links", 54 targetDid, 55 "app.bsky.graph.follow", 56 ".subject", 57 undefined, 58 [agent.did], 59 ] as const; 60 61 const updateCache = ( 62 updater: ( 63 oldData: linksRecordsResponse | undefined 64 ) => linksRecordsResponse | undefined 65 ) => { 66 queryClient.setQueryData( 67 queryKey, 68 (oldData: linksRecordsResponse | undefined) => updater(oldData) 69 ); 70 }; 71 72 if (typeof followRecords === "undefined") { 73 const newRecord = { 74 repo: agent.did, 75 collection: "app.bsky.graph.follow", 76 rkey: TID.next().toString(), 77 record: { 78 $type: "app.bsky.graph.follow", 79 subject: targetDid, 80 createdAt: new Date().toISOString(), 81 }, 82 }; 83 84 updateCache((old) => { 85 const newLinkingRecords = [newRecord, ...(old?.linking_records ?? [])]; 86 return { 87 ...old, 88 linking_records: newLinkingRecords, 89 } as linksRecordsResponse; 90 }); 91 92 agent.com.atproto.repo.createRecord(newRecord).catch((err) => { 93 console.error("Follow failed, reverting cache:", err); 94 // rollback cache 95 updateCache((old) => { 96 return { 97 ...old, 98 linking_records: 99 old?.linking_records.filter((r) => r.rkey !== newRecord.rkey) ?? [], 100 } as linksRecordsResponse; 101 }); 102 }); 103 104 return; 105 } 106 107 followRecords.forEach((followRecord) => { 108 const aturi = new AtUri(followRecord); 109 agent.com.atproto.repo 110 .deleteRecord({ 111 repo: agent.did!, 112 collection: "app.bsky.graph.follow", 113 rkey: aturi.rkey, 114 }) 115 .catch(console.error); 116 }); 117 118 updateCache((old) => { 119 if (!old?.linking_records) return old; 120 return { 121 ...old, 122 linking_records: old.linking_records.filter( 123 (rec) => 124 !followRecords.includes( 125 `at://${rec.did}/${rec.collection}/${rec.rkey}` 126 ) 127 ), 128 }; 129 }); 130} 131 132 133 134export function useGetOneToOneState(params?: { 135 target: string; 136 user: string; 137 collection: string; 138 path: string; 139}): string[] | undefined { 140 const { data: arbitrarydata } = useQueryConstellation( 141 params && params.user 142 ? { 143 method: "/links", 144 target: params.target, 145 // @ts-expect-error overloading sucks so much 146 collection: params.collection, 147 path: params.path, 148 dids: [params.user], 149 } 150 : { method: "undefined", target: "whatever" } 151 // overloading sucks so much 152 ) as { data: linksRecordsResponse | undefined }; 153 if (!params || !params.user) return undefined; 154 const data = arbitrarydata?.linking_records.slice(0, 50) ?? []; 155 156 if (data.length > 0) { 157 return data.map((linksRecord) => { 158 return `at://${linksRecord.did}/${linksRecord.collection}/${linksRecord.rkey}`; 159 }); 160 } 161 162 return undefined; 163}