an independent Bluesky client using Constellation, PDS Queries, and other services reddwarf.app
frontend spa bluesky reddwarf microcosm client app
94
fork

Configure Feed

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

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