[Chat] Gate group chats for under-18 users (#10548)
Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
@@ -20,6 +20,7 @@ import {useMessagesEventBus} from '#/state/messages/events'
|
||||
import {useModerationOpts} from '#/state/preferences/moderation-opts'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {parseConvoView} from '#/components/dms/util'
|
||||
import {useAgeAssurance} from '#/ageAssurance'
|
||||
import * as bsky from '#/types/bsky'
|
||||
import {RQKEY as CONVO_KEY} from './conversation'
|
||||
import {useLeftConvos} from './leave-conversation'
|
||||
@@ -122,10 +123,12 @@ export function ListConvosProviderInner({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
const aa = useAgeAssurance()
|
||||
const {refetch, data} = useListConvosQuery({
|
||||
readState: 'unread',
|
||||
limit: UNREAD_LIMIT,
|
||||
lockStatus: 'unlocked',
|
||||
kind: aa.flags.groupChatDisabled ? 'direct' : 'all',
|
||||
})
|
||||
const messagesBus = useMessagesEventBus()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -3,23 +3,71 @@ import {type ChatBskyActorDeclaration} from '@atproto/api'
|
||||
|
||||
import {networkRetry} from '#/lib/async/retry'
|
||||
import {logger} from '#/logger'
|
||||
import {setOtherRequiredDataActorDeclarationCache} from '#/ageAssurance/data'
|
||||
import {
|
||||
getDidFromAgentSession,
|
||||
getOtherRequiredDataFromCache,
|
||||
setOtherRequiredDataActorDeclarationCache,
|
||||
} from '#/ageAssurance/data'
|
||||
|
||||
/**
|
||||
* Helper to update the chat settings record.
|
||||
* Updates the chat actor declaration record to restrict who can contact the
|
||||
* user. Both restrictions write to the same record (`rkey: 'self'`), so this
|
||||
* is a single helper to avoid two concurrent `putRecord` calls racing each
|
||||
* other and clobbering one another's changes.
|
||||
*
|
||||
* - `restrictIncoming`: sets `allowIncoming: 'none'` (used when a user isn't
|
||||
* age-assured).
|
||||
* - `restrictGroupInvites`: sets `allowGroupInvites: 'none'` (used for under-18
|
||||
* users, who per spec cannot participate in group chats).
|
||||
*
|
||||
* Dimensions that aren't being restricted preserve their cached value, falling
|
||||
* back to the lexicon defaults when the cache is empty.
|
||||
*/
|
||||
export async function restrictChatSettings({
|
||||
agent,
|
||||
did,
|
||||
restrictIncoming = false,
|
||||
restrictGroupInvites = false,
|
||||
}: {
|
||||
agent: AtpAgent
|
||||
did: string
|
||||
restrictIncoming?: boolean
|
||||
restrictGroupInvites?: boolean
|
||||
}): Promise<void> {
|
||||
const did = getDidFromAgentSession(agent)
|
||||
if (!did) return
|
||||
|
||||
const cached = getOtherRequiredDataFromCache({did})?.actorDeclaration
|
||||
|
||||
// When the cache is empty we fall back to defaults for any dimension we're
|
||||
// not explicitly restricting, which could drop/downgrade a value the user
|
||||
// has actually set server-side. The cache should be hydrated by
|
||||
// prefetchOtherRequiredData before any of these paths fire, so log if that
|
||||
// assumption ever breaks. (Restricting both dimensions is unaffected, so
|
||||
// don't warn for the common signup/birthdate-change case.)
|
||||
if (!cached && (!restrictIncoming || !restrictGroupInvites)) {
|
||||
logger.warn(
|
||||
`restrictChatSettings: cache miss, falling back to defaults for unrestricted dimensions`,
|
||||
)
|
||||
}
|
||||
|
||||
const record: ChatBskyActorDeclaration.Main = {
|
||||
$type: 'chat.bsky.actor.declaration',
|
||||
allowIncoming: restrictIncoming
|
||||
? 'none'
|
||||
: (cached?.allowIncoming ?? 'following'),
|
||||
allowGroupInvites: restrictGroupInvites
|
||||
? 'none'
|
||||
: cached?.allowGroupInvites,
|
||||
}
|
||||
|
||||
// Nothing to do if the record already reflects the desired restrictions.
|
||||
if (
|
||||
cached?.allowIncoming === record.allowIncoming &&
|
||||
cached?.allowGroupInvites === record.allowGroupInvites
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const record: ChatBskyActorDeclaration.Main = {
|
||||
$type: 'chat.bsky.actor.declaration',
|
||||
allowIncoming: 'none',
|
||||
}
|
||||
await networkRetry(3, () =>
|
||||
agent.com.atproto.repo.putRecord({
|
||||
repo: did,
|
||||
|
||||
@@ -1,9 +1,22 @@
|
||||
import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation'
|
||||
import {DEFAULT_LABEL_SETTINGS} from '@atproto/api'
|
||||
|
||||
import {
|
||||
type ThreadViewPreferences,
|
||||
type UsePreferencesQueryResponse,
|
||||
} from '#/state/queries/preferences/types'
|
||||
|
||||
/**
|
||||
* More strict than our default settings for logged in users.
|
||||
*
|
||||
* Defined here rather than in `./moderation` to avoid a module-init cycle:
|
||||
* `moderation` imports `./index`, which re-exports this file, so reading the
|
||||
* value from `moderation` at init time lands in its temporal dead zone.
|
||||
*/
|
||||
export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_SETTINGS =
|
||||
Object.fromEntries(
|
||||
Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']),
|
||||
)
|
||||
|
||||
export const DEFAULT_HOME_FEED_PREFS: UsePreferencesQueryResponse['feedViewPrefs'] =
|
||||
{
|
||||
hideReplies: false,
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
import {useMemo} from 'react'
|
||||
import {
|
||||
BskyAgent,
|
||||
DEFAULT_LABEL_SETTINGS,
|
||||
interpretLabelValueDefinitions,
|
||||
} from '@atproto/api'
|
||||
import {BskyAgent, interpretLabelValueDefinitions} from '@atproto/api'
|
||||
|
||||
import {isNonConfigurableModerationAuthority} from '#/state/session/additional-moderation-authorities'
|
||||
import {useLabelersDetailedInfoQuery} from '../labeler'
|
||||
import {usePreferencesQuery} from './index'
|
||||
|
||||
/**
|
||||
* More strict than our default settings for logged in users.
|
||||
*/
|
||||
export const DEFAULT_LOGGED_OUT_LABEL_PREFERENCES: typeof DEFAULT_LABEL_SETTINGS =
|
||||
Object.fromEntries(
|
||||
Object.entries(DEFAULT_LABEL_SETTINGS).map(([key, _pref]) => [key, 'hide']),
|
||||
)
|
||||
|
||||
export function useMyLabelersQuery({
|
||||
excludeNonConfigurableLabelers = false,
|
||||
}: {
|
||||
|
||||
Reference in New Issue
Block a user