Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at post-text-option 217 lines 5.9 kB view raw
1import {AppBskyGraphVerification, AtUri} from '@atproto/api' 2import { 3 type VerificationState, 4 type VerificationView, 5} from '@atproto/api/dist/client/types/app/bsky/actor/defs' 6import {useQuery} from '@tanstack/react-query' 7 8import {STALE} from '#/state/queries' 9import * as bsky from '#/types/bsky' 10import {type AnyProfileView} from '#/types/bsky/profile' 11import {useConstellationInstance} from '../preferences/constellation-instance' 12import { 13 useDeerVerificationEnabled, 14 useDeerVerificationTrusted, 15} from '../preferences/deer-verification' 16import { 17 asUri, 18 asyncGenCollect, 19 asyncGenDedupe, 20 asyncGenFilter, 21 asyncGenTryMap, 22 type ConstellationLink, 23 constellationLinks, 24} from './constellation' 25import {LRU} from './direct-fetch-record' 26import {resolvePdsServiceUrl} from './resolve-identity' 27import {useCurrentAccountProfile} from './useCurrentAccountProfile' 28 29const RQKEY_ROOT = 'deer-verification' 30export const RQKEY = (did: string, trusted: Set<string>) => [ 31 RQKEY_ROOT, 32 did, 33 Array.from(trusted).sort(), 34] 35 36type LinkedRecord = { 37 link: ConstellationLink 38 record: AppBskyGraphVerification.Record 39} 40 41const verificationCache = new LRU<string, any>() 42 43export function getTrustedConstellationVerifications( 44 instance: string, 45 did: string, 46 trusted: Set<string>, 47) { 48 const urip = new AtUri(did) 49 const verificationLinks = constellationLinks(instance, { 50 target: urip.host, 51 collection: 'app.bsky.graph.verification', 52 path: '.subject', 53 from_dids: Array.from(trusted), 54 }) 55 return asyncGenDedupe( 56 asyncGenFilter(verificationLinks, ({did}) => trusted.has(did)), 57 ({did}) => did, 58 ) 59} 60 61async function getDeerVerificationLinkedRecords( 62 instance: string, 63 did: string, 64 trusted: Set<string>, 65): Promise<LinkedRecord[] | undefined> { 66 try { 67 const trustedVerificationLinks = getTrustedConstellationVerifications( 68 instance, 69 did, 70 trusted, 71 ) 72 73 const verificationRecords = asyncGenFilter( 74 asyncGenTryMap<ConstellationLink, {link: ConstellationLink; record: any}>( 75 trustedVerificationLinks, 76 // using try map lets us: 77 // - cache the service url and verificatin record in independent lrus 78 // - clear the promise from the lru on failure 79 // - skip links that cause errors 80 async link => { 81 const {did, rkey} = link 82 83 let service = await resolvePdsServiceUrl(did) 84 85 const request = `${service}/xrpc/com.atproto.repo.getRecord?repo=${did}&collection=app.bsky.graph.verification&rkey=${rkey}` 86 const record = await verificationCache.getOrTryInsertWith( 87 request, 88 async () => { 89 const resp = await (await fetch(request)).json() 90 return resp.value 91 }, 92 ) 93 return {link, record} 94 }, 95 (_, e) => { 96 console.error(e) 97 }, 98 ), 99 // the explicit return type shouldn't be needed... 100 (d: {link: ConstellationLink; record: unknown}): d is LinkedRecord => 101 bsky.validate<AppBskyGraphVerification.Record>( 102 d.record, 103 AppBskyGraphVerification.validateRecord, 104 ), 105 ) 106 107 // Array.fromAsync will do this but not available everywhere yet 108 return asyncGenCollect(verificationRecords) 109 } catch (e) { 110 console.error(e) 111 return undefined 112 } 113} 114 115function createVerificationViews( 116 linkedRecords: LinkedRecord[], 117 profile: AnyProfileView, 118): VerificationView[] { 119 return linkedRecords.map(({link, record}) => ({ 120 issuer: link.did, 121 isValid: 122 (profile.displayName ?? '') === record.displayName && 123 profile.handle === record.handle, 124 createdAt: record.createdAt, 125 uri: asUri(link), 126 })) 127} 128 129function createVerificationState( 130 verifications: VerificationView[], 131 profile: AnyProfileView, 132 trusted: Set<string>, 133): VerificationState { 134 return { 135 verifications, 136 verifiedStatus: 137 verifications.length > 0 138 ? verifications.findIndex(v => v.isValid) !== -1 139 ? 'valid' 140 : 'invalid' 141 : 'none', 142 trustedVerifierStatus: trusted.has(profile.did) ? 'valid' : 'none', 143 } 144} 145 146export function useDeerVerificationState({ 147 profile, 148 enabled, 149}: { 150 profile: AnyProfileView | undefined 151 enabled?: boolean 152}) { 153 const instance = useConstellationInstance() 154 const currentAccountProfile = useCurrentAccountProfile() 155 const trusted = useDeerVerificationTrusted(currentAccountProfile?.did) 156 157 const linkedRecords = useQuery<LinkedRecord[] | undefined>({ 158 staleTime: STALE.HOURS.ONE, 159 queryKey: RQKEY(profile?.did || '', trusted), 160 async queryFn() { 161 if (!profile) return undefined 162 163 return await getDeerVerificationLinkedRecords( 164 instance, 165 profile.did, 166 trusted, 167 ) 168 }, 169 enabled: enabled && profile !== undefined, 170 }) 171 172 if (linkedRecords.data === undefined || profile === undefined) return 173 const verifications = createVerificationViews(linkedRecords.data, profile) 174 const verificationState = createVerificationState( 175 verifications, 176 profile, 177 trusted, 178 ) 179 180 return verificationState 181} 182 183export function useDeerVerificationProfileOverlay<V extends AnyProfileView>( 184 profile: V, 185): V { 186 const enabled = useDeerVerificationEnabled() 187 const verificationState = useDeerVerificationState({ 188 profile, 189 enabled, 190 }) 191 192 return enabled 193 ? { 194 ...profile, 195 verification: verificationState, 196 } 197 : profile 198} 199 200export function useMaybeDeerVerificationProfileOverlay< 201 V extends AnyProfileView, 202>(profile: V | undefined): V | undefined { 203 const enabled = useDeerVerificationEnabled() 204 const verificationState = useDeerVerificationState({ 205 profile, 206 enabled, 207 }) 208 209 if (!profile) return undefined 210 211 return enabled 212 ? { 213 ...profile, 214 verification: verificationState, 215 } 216 : profile 217}