an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
at main 228 lines 8.3 kB view raw
1import { useQueries } from "@tanstack/react-query"; 2import { useAtom, useSetAtom } from "jotai"; 3import { useEffect, useRef } from "react"; 4 5import { FORCED_LABELER_DIDS, UNAUTHED_FORCE_WARN_LABELS } from "~/../policy"; 6import { useAuth } from "~/providers/UnifiedAuthProvider"; 7import { labelerConfigAtom } from "~/state/moderationAtoms"; 8import type { LabelerDefinition, LabelPreference, LabelValueDefinition } from "~/types/moderation"; 9import { slingshotURLAtom } from "~/utils/atoms"; 10import { useQueryIdentity } from "~/utils/useQuery"; 11import { useQueryPreferences } from "~/utils/useQuery"; 12 13// Manual DID document resolution 14const fetchDidDocument = async (did: string): Promise<any> => { 15 if (did.startsWith("did:plc:")) { 16 const response = await fetch( 17 `https://plc.directory/${encodeURIComponent(did)}`, 18 ); 19 if (!response.ok) 20 throw new Error(`Failed to fetch PLC DID document for ${did}`); 21 return response.json(); 22 } else if (did.startsWith("did:web:")) { 23 const handle = did.replace("did:web:", ""); 24 const url = `https://${handle}/.well-known/did.json`; 25 const response = await fetch(url); 26 if (!response.ok) 27 throw new Error( 28 `Failed to fetch web DID document for ${did} (CORS or not found)`, 29 ); 30 return response.json(); 31 } else { 32 throw new Error(`Unsupported DID type: ${did}`); 33 } 34}; 35 36export const ModerationInitializer = () => { 37 const { agent, status } = useAuth(); 38 const setLabelerConfig = useSetAtom(labelerConfigAtom); 39 const [slingshoturl] = useAtom(slingshotURLAtom); 40 41 // Define clear boolean for mode 42 const isUnauthed = status === "signedOut" || !agent; 43 44 // Track previous status to detect transitions 45 const prevStatusRef = useRef(status); 46 47 // --- 1. THE HARD FLUSH --- 48 // When Auth Status changes (Logged In <-> Logged Out), immediately wipe the config. 49 // This prevents "Authed" prefs from bleeding into "Unauthed" state and vice versa 50 // while the async queries are spinning up. 51 useEffect(() => { 52 if (prevStatusRef.current !== status) { 53 console.log(`[Moderation] Auth status changed (${prevStatusRef.current} -> ${status}). Flushing config.`); 54 setLabelerConfig([]); // <--- WIPE CLEAN 55 prevStatusRef.current = status; 56 } 57 }, [status, setLabelerConfig]); 58 59 // 2. Get User Identity (Only if authed) 60 const { data: identity } = useQueryIdentity(agent?.did); 61 62 // 3. Get User Preferences (Only if authed) 63 const { data: prefs } = useQueryPreferences({ 64 agent: agent ?? undefined, 65 pdsUrl: identity?.pds, 66 }); 67 68 // 4. Identify Labeler DIDs 69 // Important: If unauthed, userPrefDids MUST be empty, even if cache exists. 70 const userPrefDids = !isUnauthed 71 ? prefs?.preferences 72 ?.find((pref: any) => pref.$type === "app.bsky.actor.defs#labelersPref") 73 ?.labelers?.map((l: any) => l.did) ?? [] 74 : []; 75 76 // 5. Force Bsky DID + User DIDs 77 const activeLabelerDids = Array.from( 78 new Set([...FORCED_LABELER_DIDS, ...userPrefDids]) 79 ); 80 81 // 6. Parallel fetch DID Docs 82 const labelerDidDocQueries = useQueries({ 83 queries: activeLabelerDids.map((did: string) => ({ 84 queryKey: ["labelerDidDoc", did], 85 queryFn: () => fetchDidDocument(did), 86 staleTime: 1000 * 60 * 60 * 24, 87 })), 88 }); 89 90 // 7. Parallel fetch Service Records 91 const labelerServiceQueries = useQueries({ 92 queries: activeLabelerDids.map((did: string) => ({ 93 queryKey: ["labelerService", did], 94 queryFn: async () => { 95 const host = slingshoturl || "public.api.bsky.app"; 96 const response = await fetch( 97 `https://${host}/xrpc/com.atproto.repo.getRecord?repo=${encodeURIComponent(did)}&collection=${encodeURIComponent("app.bsky.labeler.service")}&rkey=self`, 98 ); 99 if (!response.ok) throw new Error("Failed to fetch labeler service"); 100 return response.json(); 101 }, 102 staleTime: 1000 * 60 * 60, 103 })), 104 }); 105 106 useEffect(() => { 107 // Guard: Wait for queries 108 if ( 109 labelerDidDocQueries.some((q) => q.isLoading) || 110 labelerServiceQueries.some((q) => q.isLoading) 111 ) { 112 return; 113 } 114 115 // Guard: If we are supposed to be Authed, but prefs haven't loaded yet, 116 // DO NOT run the logic. Wait. This prevents falling back to defaults temporarily. 117 if (!isUnauthed && !prefs) { 118 return; 119 } 120 121 // A. Extract User Global Overrides 122 // STRICT SEPARATION: If unauthed, force this to be empty to ensure no leakage. 123 const globalPrefs: Record<string, LabelPreference> = {}; 124 125 if (!isUnauthed && prefs?.preferences) { 126 const contentLabelPrefs = prefs.preferences.filter( 127 (pref: any) => pref.$type === "app.bsky.actor.defs#contentLabelPref", 128 ); 129 contentLabelPrefs.forEach((pref: any) => { 130 globalPrefs[pref.label] = pref.visibility as LabelPreference; 131 }); 132 } 133 134 const definitions: LabelerDefinition[] = activeLabelerDids 135 .map((did: string, index: number) => { 136 const didDocQuery = labelerDidDocQueries[index]; 137 const serviceQuery = labelerServiceQueries[index]; 138 139 if (!didDocQuery.data || !serviceQuery.data) return null; 140 141 const didDoc = didDocQuery.data as any; 142 const atprotoLabelerService = didDoc?.service?.find( 143 (s: any) => s.id === "#atproto_labeler", 144 ); 145 146 const record = (serviceQuery.data as any).value; 147 148 // B. Gather ALL identifiers 149 const allIdentifiers = new Set<string>(); 150 record.policies?.labelValues?.forEach((val: string) => allIdentifiers.add(val)); 151 record.policies?.labelValueDefinitions?.forEach((def: any) => allIdentifiers.add(def.identifier)); 152 153 // C. Create Metadata Map 154 const labelDefs: Record<string, LabelValueDefinition> = {}; 155 if (record.policies.labelValueDefinitions) { 156 record.policies.labelValueDefinitions.forEach((def: any) => { 157 labelDefs[def.identifier] = { 158 identifier: def.identifier, 159 severity: def.severity, 160 blurs: def.blurs, 161 adultOnly: def.adultOnly, 162 defaultSetting: def.defaultSetting, 163 locales: def.locales || [] 164 }; 165 }); 166 } 167 168 // D. Resolve Preferences 169 const supportedLabels: Record<string, LabelPreference> = {}; 170 171 allIdentifiers.forEach((val) => { 172 // todo this works but with how useModeration hooks works right now old verdicts wont get stale-d 173 // it only works right now because these are warns and warns are negligable i guess 174 // --- BRANCH 1: UNAUTHED MODE --- 175 if (isUnauthed) { 176 // 1. Strict Force Overrides 177 if (UNAUTHED_FORCE_WARN_LABELS.has(val)) { 178 supportedLabels[val] = "warn"; // or 'hide' if that's what your policy constant implies 179 return; 180 } 181 182 // 2. Default Labeler Settings 183 const def = labelDefs[val]; 184 const rawDefault = def?.defaultSetting || "ignore"; 185 186 // 3. Apply Unauthed-Specific Aliasing (Optional) 187 // e.g., if you want to hide 'inform' labels for unauthed users 188 supportedLabels[val] = rawDefault as LabelPreference; 189 return; 190 } 191 192 // --- BRANCH 2: AUTHED MODE --- 193 // 1. User Global Override (Highest Priority) 194 const globalPref = globalPrefs[val]; 195 if (globalPref) { 196 supportedLabels[val] = globalPref; 197 return; 198 } 199 200 // 2. Labeler Default 201 const def = labelDefs[val]; 202 const rawDefault = def?.defaultSetting || "ignore"; 203 204 supportedLabels[val] = rawDefault as LabelPreference; 205 }); 206 207 return { 208 did: did, 209 url: atprotoLabelerService?.serviceEndpoint || record.serviceEndpoint, 210 isDefault: FORCED_LABELER_DIDS.includes(did), 211 supportedLabels, 212 labelDefs, 213 }; 214 }) 215 .filter(Boolean) as LabelerDefinition[]; 216 217 setLabelerConfig(definitions); 218 }, [ 219 prefs, 220 labelerDidDocQueries, 221 labelerServiceQueries, 222 setLabelerConfig, 223 activeLabelerDids, 224 isUnauthed // <--- Critical dependency triggers re-eval on login/out 225 ]); 226 227 return null; 228};