Override prefs in all locations

This commit is contained in:
Eric Bailey
2025-07-08 14:12:19 -05:00
parent 6dcac74691
commit c2c7138987
5 changed files with 104 additions and 39 deletions
+19 -6
View File
@@ -2,7 +2,7 @@ import {createContext, useContext, useMemo} from 'react'
import {type AppBskyUnspeccedDefs} from '@atproto/api'
import {useQuery, useQueryClient} from '@tanstack/react-query'
import {wait} from '#/lib/async/wait'
// import {wait} from '#/lib/async/wait'
import {isNetworkError} from '#/lib/strings/errors'
import {Logger} from '#/logger'
import {
@@ -39,14 +39,27 @@ export function Provider({children}: {children: React.ReactNode}) {
const {geolocation} = useGeolocation()
const {data, isFetched, refetch} = useQuery({
enabled: !!agent.session,
/**
* This is load bearing. We always want this query to run and end in a
* "fetched" state, even if we fall back to defaults. This lets the rest of
* the app know that we've at least attempted to load the AA state.
*/
enabled: true,
queryKey: createAgeAssuranceQueryKey(agent.session?.did ?? 'never'),
async queryFn() {
if (!agent.session) return null
try {
const {data} = await wait(
1e3,
agent.app.bsky.unspecced.getAgeAssuranceState(),
)
const {data} = await agent.app.bsky.unspecced.getAgeAssuranceState()
// const {data} = await wait(
// 1e3,
// (() => ({
// data: {
// lastInitiatedAt: new Date().toISOString(),
// status: 'assured',
// } as AppBskyUnspeccedDefs.AgeAssuranceState,
// }))(),
// )
logger.debug(`fetch`, {
data,
@@ -1,21 +1,42 @@
import {useCallback} from 'react'
import {type ModerationPrefs} from '@atproto/api'
// import {Logger} from '#/logger'
import {useAgeAssuranceContext} from '#/state/age-assurance'
import {AGE_RESTRICTED_MODERATION_PREFS} from '#/state/age-assurance/const'
// const logger = Logger.create(Logger.Context.AgeAssurance)
/**
* Hook to conditionally apply age-restricted moderation preferences, if
* needed. If not needed, the mod prefs passed to the callback will be used.
*/
export function useMaybeApplyAgeRestrictedModerationPrefs() {
const {isLoaded, isAgeRestricted, isExempt} = useAgeAssuranceContext()
const state = useAgeAssuranceContext()
return useCallback(
(prev: ModerationPrefs) => {
const isDefinitelyAgeRestricted = isLoaded && isAgeRestricted
const notSureYet = !isLoaded && isAgeRestricted
const isDefinitelyAgeRestricted = state.isLoaded && state.isAgeRestricted
const notSureYet = !state.isLoaded && state.isAgeRestricted
if (isExempt) return prev
if (notSureYet || isDefinitelyAgeRestricted)
if (state.isExempt) {
// logger.debug('useMaybeApplyAgeRestrictedModerationPrefs: exempt', state)
return prev
}
if (notSureYet || isDefinitelyAgeRestricted) {
// logger.debug(
// 'useMaybeApplyAgeRestrictedModerationPrefs: overridden',
// state,
// )
return AGE_RESTRICTED_MODERATION_PREFS
}
// logger.debug(
// 'useMaybeApplyAgeRestrictedModerationPrefs: not overridden',
// state,
// )
return prev
},
[isLoaded, isAgeRestricted, isExempt],
[state],
)
}
+23 -11
View File
@@ -1,19 +1,19 @@
import React from 'react'
import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
import {
type AppBskyActorDefs,
moderateProfile,
type ModerationOpts,
} from '@atproto/api'
import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query'
import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
import {logger} from '#/logger'
import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {STALE} from '#/state/queries'
import {usePreferencesQuery} from '#/state/queries/preferences'
import {useAgent} from '#/state/session'
import {useModerationOpts} from '../preferences/moderation-opts'
import {DEFAULT_LOGGED_OUT_PREFERENCES} from './preferences'
const DEFAULT_MOD_OPTS = {
userDid: undefined,
prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
}
const RQKEY_ROOT = 'actor-autocomplete'
export const RQKEY = (prefix: string) => [RQKEY_ROOT, prefix]
@@ -24,6 +24,7 @@ export function useActorAutocompleteQuery(
) {
const moderationOpts = useModerationOpts()
const agent = useAgent()
const {data: preferences} = usePreferencesQuery()
prefix = prefix.toLowerCase().trim()
if (prefix.endsWith('.')) {
@@ -48,10 +49,15 @@ export function useActorAutocompleteQuery(
return computeSuggestions({
q: prefix,
searched: data,
moderationOpts: moderationOpts || DEFAULT_MOD_OPTS,
moderationOpts: moderationOpts || {
userDid: undefined,
prefs:
preferences?.moderationPrefs ||
DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
},
})
},
[prefix, moderationOpts],
[prefix, moderationOpts, preferences],
),
placeholderData: maintainData ? keepPreviousData : undefined,
})
@@ -62,6 +68,7 @@ export function useActorAutocompleteFn() {
const queryClient = useQueryClient()
const moderationOpts = useModerationOpts()
const agent = useAgent()
const {data: preferences} = usePreferencesQuery()
return React.useCallback(
async ({query, limit = 8}: {query: string; limit?: number}) => {
@@ -88,10 +95,15 @@ export function useActorAutocompleteFn() {
return computeSuggestions({
q: query,
searched: res?.data.actors,
moderationOpts: moderationOpts || DEFAULT_MOD_OPTS,
moderationOpts: moderationOpts || {
userDid: undefined,
prefs:
preferences?.moderationPrefs ||
DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
},
})
},
[queryClient, moderationOpts, agent],
[queryClient, moderationOpts, agent, preferences],
)
}
+11 -3
View File
@@ -8,6 +8,7 @@ import {
type BskyAgent,
moderatePost,
type ModerationDecision,
type ModerationPrefs,
} from '@atproto/api'
import {
type InfiniteData,
@@ -206,7 +207,11 @@ export function usePostFeedQuery(
* some not.
*/
if (!agent.session) {
assertSomePostsPassModeration(res.feed)
assertSomePostsPassModeration(
res.feed,
preferences?.moderationPrefs ||
DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
)
}
return {
@@ -596,7 +601,10 @@ export function* findAllProfilesInQueryData(
}
}
function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
function assertSomePostsPassModeration(
feed: AppBskyFeedDefs.FeedViewPost[],
moderationPrefs: ModerationPrefs,
) {
// no posts in this feed
if (feed.length === 0) return true
@@ -606,7 +614,7 @@ function assertSomePostsPassModeration(feed: AppBskyFeedDefs.FeedViewPost[]) {
for (const item of feed) {
const moderation = moderatePost(item.post, {
userDid: undefined,
prefs: DEFAULT_LOGGED_OUT_PREFERENCES.moderationPrefs,
prefs: moderationPrefs,
})
if (!moderation.ui('contentList').filter) {
+24 -13
View File
@@ -1,3 +1,4 @@
import {useCallback} from 'react'
import {
type AppBskyActorDefs,
type BskyFeedViewPreference,
@@ -9,6 +10,7 @@ import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {replaceEqualDeep} from '#/lib/functions'
import {getAge} from '#/lib/strings/time'
import {logger} from '#/logger'
import {useAgeAssuranceContext} from '#/state/age-assurance'
import {useMaybeApplyAgeRestrictedModerationPrefs} from '#/state/age-assurance/useMaybeApplyAgeRestrictedModerationPrefs'
import {STALE} from '#/state/queries'
import {
@@ -32,16 +34,23 @@ export const preferencesQueryKey = [preferencesQueryKeyRoot]
export function usePreferencesQuery() {
const agent = useAgent()
const overrideModPrefs = useMaybeApplyAgeRestrictedModerationPrefs()
const query = useQuery({
const {isLoaded: isAAStateLoaded} = useAgeAssuranceContext()
const maybeOverrideModPrefs = useMaybeApplyAgeRestrictedModerationPrefs()
return useQuery({
/*
* We must await AA state, otherwise users may see a FOUC - esb
*/
enabled: isAAStateLoaded,
staleTime: STALE.SECONDS.FIFTEEN,
structuralSharing: replaceEqualDeep,
refetchOnWindowFocus: true,
queryKey: preferencesQueryKey,
queryFn: async () => {
if (!agent.did) {
return DEFAULT_LOGGED_OUT_PREFERENCES
} else {
let preferences: UsePreferencesQueryResponse =
DEFAULT_LOGGED_OUT_PREFERENCES
if (agent.did) {
const res = await agent.getPreferences()
// save to local storage to ensure there are labels on initial requests
@@ -50,7 +59,7 @@ export function usePreferencesQuery() {
res.moderationPrefs.labelers.map(l => l.did),
)
const preferences: UsePreferencesQueryResponse = {
preferences = {
...res,
savedFeeds: res.savedFeeds.filter(f => f.type !== 'unknown'),
/**
@@ -67,16 +76,18 @@ export function usePreferencesQuery() {
},
userAge: res.birthDate ? getAge(res.birthDate) : undefined,
}
return preferences
}
return preferences
},
select: useCallback(
(data: UsePreferencesQueryResponse) => {
data.moderationPrefs = maybeOverrideModPrefs(data.moderationPrefs)
return data
},
[maybeOverrideModPrefs],
),
})
if (query.data) {
query.data.moderationPrefs = overrideModPrefs(query.data.moderationPrefs)
}
return query
}
export function useClearPreferencesMutation() {