From 84c4223ff937314b08514c9ea11d95825d1d2887 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 9 Jun 2026 16:31:22 +0300 Subject: [PATCH] [Chat] Gate group chats for under-18 users (#10548) Co-authored-by: Eric Bailey --- eslint-suppressions.json | 11 ---- src/ageAssurance/const.ts | 28 ++++++++ src/ageAssurance/index.tsx | 27 ++++++-- src/ageAssurance/types.ts | 2 + src/ageAssurance/util.ts | 49 +++----------- src/components/dms/InitiateChatFlow.tsx | 9 ++- src/screens/Messages/ChatList.tsx | 4 ++ src/screens/Messages/Settings.tsx | 24 +++++-- src/screens/Signup/StepInfo/index.tsx | 2 +- src/state/birthdate.ts | 9 ++- src/state/preferences/moderation-opts.tsx | 2 +- .../queries/messages/list-conversations.tsx | 3 + .../queries/messages/restrictChatSettings.ts | 64 ++++++++++++++++--- src/state/queries/preferences/const.ts | 15 ++++- src/state/queries/preferences/moderation.ts | 14 +--- src/state/session/agent.ts | 13 ++-- 16 files changed, 180 insertions(+), 96 deletions(-) create mode 100644 src/ageAssurance/const.ts diff --git a/eslint-suppressions.json b/eslint-suppressions.json index 304fd4a065..1eea217133 100644 --- a/eslint-suppressions.json +++ b/eslint-suppressions.json @@ -19,11 +19,6 @@ "count": 1 } }, - "src/ageAssurance/util.ts": { - "@typescript-eslint/no-floating-promises": { - "count": 1 - } - }, "src/alf/util/flatten.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 @@ -1903,12 +1898,6 @@ "src/state/session/agent.ts": { "@typescript-eslint/no-explicit-any": { "count": 1 - }, - "@typescript-eslint/no-floating-promises": { - "count": 1 - }, - "@typescript-eslint/require-await": { - "count": 1 } }, "src/state/shell/color-mode.tsx": { diff --git a/src/ageAssurance/const.ts b/src/ageAssurance/const.ts new file mode 100644 index 0000000000..bd79353b51 --- /dev/null +++ b/src/ageAssurance/const.ts @@ -0,0 +1,28 @@ +import { + ageAssuranceRuleIDs as ids, + type AppBskyAgeassuranceDefs, +} from '@atproto/api' + +import {AgeAssuranceAccess} from '#/ageAssurance/types' + +/** + * Minimum age required to access the app at all. + */ +export const MIN_ACCESS_AGE = 13 + +export const FALLBACK_REGION_CONFIG: AppBskyAgeassuranceDefs.ConfigRegion = { + countryCode: '*', + regionCode: undefined, + minAccessAge: MIN_ACCESS_AGE, + rules: [ + { + $type: ids.IfDeclaredOverAge, + age: MIN_ACCESS_AGE, + access: AgeAssuranceAccess.Full, + }, + { + $type: ids.Default, + access: AgeAssuranceAccess.None, + }, + ], +} diff --git a/src/ageAssurance/index.tsx b/src/ageAssurance/index.tsx index 2b10c93a5d..20bcc2bdb8 100644 --- a/src/ageAssurance/index.tsx +++ b/src/ageAssurance/index.tsx @@ -1,6 +1,7 @@ import {createContext, useCallback, useContext, useMemo} from 'react' import {useGetAndRegisterPushToken} from '#/lib/notifications/notifications' +import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' import {useAgent} from '#/state/session' import {Provider as RedirectOverlayProvider} from '#/ageAssurance/components/RedirectOverlay' import { @@ -20,7 +21,6 @@ import { } from '#/ageAssurance/types' import { computeAgeAssuranceFlags, - maybeRestrictChatSettings, useAgeAssuranceRegionConfigWithFallback, } from '#/ageAssurance/util' @@ -32,7 +32,6 @@ export { usePatchServerState as usePatchAgeAssuranceServerState, } from '#/ageAssurance/data' export {logger} from '#/ageAssurance/logger' -export {MIN_ACCESS_AGE} from '#/ageAssurance/util' const AgeAssuranceStateContext = createContext<{ Access: typeof AgeAssuranceAccess @@ -48,8 +47,10 @@ const AgeAssuranceStateContext = createContext<{ access: AgeAssuranceAccess.Full, }, flags: { + isAgeRestricted: false, adultContentDisabled: false, chatDisabled: false, + groupChatDisabled: false, isDeclaredUnderAdultAge: false, isOverRegionMinAccessAge: false, isOverAppMinAccessAge: false, @@ -84,13 +85,25 @@ function InnerProvider({children}: {children: React.ReactNode}) { const handleAccessUpdate = useCallback( (s: AgeAssuranceState) => { - const isAgeRestricted = s.access !== AgeAssuranceAccess.Full - if (isAgeRestricted) { - void getAndRegisterPushToken({isAgeRestricted}) - maybeRestrictChatSettings({agent}) + const flags = computeAgeAssuranceFlags({ + state: s, + regionConfig, + metadata, + }) + if (flags.isAgeRestricted) { + void getAndRegisterPushToken({ + isAgeRestricted: true, + }) + } + if (flags.chatDisabled || flags.groupChatDisabled) { + void restrictChatSettings({ + agent, + restrictIncoming: flags.chatDisabled, + restrictGroupInvites: flags.groupChatDisabled, + }) } }, - [agent, getAndRegisterPushToken], + [agent, getAndRegisterPushToken, regionConfig, metadata], ) useOnAgeAssuranceAccessUpdate(handleAccessUpdate) diff --git a/src/ageAssurance/types.ts b/src/ageAssurance/types.ts index 12473bc2e6..21abff89f0 100644 --- a/src/ageAssurance/types.ts +++ b/src/ageAssurance/types.ts @@ -30,8 +30,10 @@ export type AgeAssuranceState = { } export type AgeAssuranceFlags = { + isAgeRestricted: boolean adultContentDisabled: boolean chatDisabled: boolean + groupChatDisabled: boolean isDeclaredUnderAdultAge: boolean isOverRegionMinAccessAge: boolean isOverAppMinAccessAge: boolean diff --git a/src/ageAssurance/util.ts b/src/ageAssurance/util.ts index b0e601d6c7..0d493f1130 100644 --- a/src/ageAssurance/util.ts +++ b/src/ageAssurance/util.ts @@ -1,20 +1,14 @@ import {useMemo} from 'react' import { - ageAssuranceRuleIDs as ids, type AppBskyAgeassuranceDefs, - type AtpAgent, getAgeAssuranceRegionConfig, type ModerationPrefs, } from '@atproto/api' import {getAge} from '#/lib/strings/time' -import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' -import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' -import { - getDidFromAgentSession, - getOtherRequiredDataFromCache, - useAgeAssuranceServerDataContext, -} from '#/ageAssurance/data' +import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/const' +import {FALLBACK_REGION_CONFIG, MIN_ACCESS_AGE} from '#/ageAssurance/const' +import {useAgeAssuranceServerDataContext} from '#/ageAssurance/data' import { AgeAssuranceAccess, type AgeAssuranceFlags, @@ -23,24 +17,6 @@ import { } from '#/ageAssurance/types' import {type Geolocation, useGeolocation} from '#/geolocation' -export const MIN_ACCESS_AGE = 13 -const FALLBACK_REGION_CONFIG: AppBskyAgeassuranceDefs.ConfigRegion = { - countryCode: '*', - regionCode: undefined, - minAccessAge: MIN_ACCESS_AGE, - rules: [ - { - $type: ids.IfDeclaredOverAge, - age: MIN_ACCESS_AGE, - access: AgeAssuranceAccess.Full, - }, - { - $type: ids.Default, - access: AgeAssuranceAccess.None, - }, - ], -} - /** * Get age assurance region config based on geolocation, with fallback to * app defaults if no region config is found. @@ -121,19 +97,6 @@ export const makeAgeRestrictedModerationPrefs = ( labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, }) -/** - * Checks our cache of the actor's chat declaration record, and if it's not - * already restricted, restricts it. - */ -export function maybeRestrictChatSettings({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) - if (!did) return - const data = getOtherRequiredDataFromCache({did}) - // ...update the chat setting record if allowIncoming is not already 'none'. - if (data?.actorDeclaration?.allowIncoming === 'none') return - restrictChatSettings({agent, did}) -} - export function computeAgeAssuranceFlags({ state, regionConfig, @@ -143,10 +106,12 @@ export function computeAgeAssuranceFlags({ regionConfig: AppBskyAgeassuranceDefs.ConfigRegion metadata?: AgeAssuranceMetadata }): AgeAssuranceFlags { - const chatDisabled = state.access !== AgeAssuranceAccess.Full + const isAgeRestricted = state.access !== AgeAssuranceAccess.Full + const chatDisabled = isAgeRestricted const isDeclaredUnderAdultAge = metadata?.declaredAge ? metadata.declaredAge < 18 : true + const groupChatDisabled = chatDisabled || isDeclaredUnderAdultAge const isOverRegionMinAccessAge = metadata?.declaredAge ? metadata.declaredAge >= regionConfig.minAccessAge : false @@ -157,8 +122,10 @@ export function computeAgeAssuranceFlags({ state.access !== AgeAssuranceAccess.Full || isDeclaredUnderAdultAge return { + isAgeRestricted, adultContentDisabled, chatDisabled, + groupChatDisabled, isDeclaredUnderAdultAge, isOverRegionMinAccessAge, isOverAppMinAccessAge, diff --git a/src/components/dms/InitiateChatFlow.tsx b/src/components/dms/InitiateChatFlow.tsx index 161b0cf722..ed519fed23 100644 --- a/src/components/dms/InitiateChatFlow.tsx +++ b/src/components/dms/InitiateChatFlow.tsx @@ -36,6 +36,7 @@ import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Ti import * as ProfileCard from '#/components/ProfileCard' import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' +import {useAgeAssurance} from '#/ageAssurance' import {IS_NATIVE, IS_WEB} from '#/env' import type * as bsky from '#/types/bsky' import {ChatProfileTabs} from './ChatProfileTabs' @@ -209,6 +210,7 @@ export function InitiateChatFlow({ const [footerHeight, setFooterHeight] = useState(0) const listRef = useRef(null) const {currentAccount} = useSession() + const aa = useAgeAssurance() const inputRef = useRef(null) const accountTooNewPromptControl = Dialog.useDialogControl() @@ -330,7 +332,11 @@ export function InitiateChatFlow({ }) } - if (chatState === ChatState.NEW_CHAT && searchText === '') { + if ( + chatState === ChatState.NEW_CHAT && + searchText === '' && + !aa.flags.groupChatDisabled + ) { _items.unshift({type: 'newGroupChat', key: 'newGroupChat'}) } @@ -344,6 +350,7 @@ export function InitiateChatFlow({ results, currentAccount?.did, follows, + aa.flags.groupChatDisabled, ]) if (searchText && !isFetching && !items.length && !isError) { diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index 387fcd2bd1..ab622f708a 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -198,6 +198,7 @@ export function ChatList({ }) { const t = useTheme() const {t: l} = useLingui() + const aa = useAgeAssurance() const scrollElRef: ListRef = useAnimatedRef() const {isWithinSplitView} = useIsWithinSplitView() @@ -230,6 +231,7 @@ export function ChatList({ const {refetch: refetchInbox} = useListConvosQuery({ status: 'request', + kind: aa.flags.groupChatDisabled ? 'direct' : 'all', }) useRefreshOnFocus(refetch) @@ -449,6 +451,7 @@ export function Header({ }) { const {t: l} = useLingui() const {gtMobile} = useBreakpoints() + const aa = useAgeAssurance() const requireEmailVerification = useRequireEmailVerification() const leftConvos = useLeftConvos() const {isWithinSplitView} = useIsWithinSplitView() @@ -462,6 +465,7 @@ export function Header({ useListConvosQuery({ status: 'request', readState: 'unread', + kind: aa.flags.groupChatDisabled ? 'direct' : 'all', }) const inboxAllConvos = diff --git a/src/screens/Messages/Settings.tsx b/src/screens/Messages/Settings.tsx index 91d9f879d5..195a87af62 100644 --- a/src/screens/Messages/Settings.tsx +++ b/src/screens/Messages/Settings.tsx @@ -21,6 +21,7 @@ import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRightIcon} from '#/compon import * as Layout from '#/components/Layout' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' +import {useAgeAssurance} from '#/ageAssurance' import {useAnalytics} from '#/analytics' import {IS_NATIVE} from '#/env' import {useBackgroundNotificationPreferences} from '../../../modules/expo-background-notification-handler/src/BackgroundNotificationHandlerProvider' @@ -46,6 +47,7 @@ export function MessagesSettingsScreenInner({}: Props) { const t = useTheme() const {t: l} = useLingui() const ax = useAnalytics() + const aa = useAgeAssurance() const {currentAccount} = useSession() const {data: profile} = useProfileQuery({ did: currentAccount!.did, @@ -55,6 +57,7 @@ export function MessagesSettingsScreenInner({}: Props) { const exportCarControl = Dialog.useDialogControl() const isGroupChatEnabled = ax.features.enabled(ax.features.GroupChatsEnable) + const groupInvitesLocked = aa.flags.groupChatDisabled const allowMessagesFromOptions: {name: AllowIncoming; label: string}[] = [ { @@ -192,15 +195,26 @@ export function MessagesSettingsScreenInner({}: Props) { a.leading_snug, t.atoms.text_contrast_high, ]}> - - You can continue ongoing conversations regardless of which - setting you choose. - + {groupInvitesLocked ? ( + + Group chats are only available to users 18 and over. + + ) : ( + + You can continue ongoing conversations regardless of which + setting you choose. + + )} {allowGroupInvitesFromOptions.map(option => ( diff --git a/src/screens/Signup/StepInfo/index.tsx b/src/screens/Signup/StepInfo/index.tsx index 65b2791c29..5d1ecae014 100644 --- a/src/screens/Signup/StepInfo/index.tsx +++ b/src/screens/Signup/StepInfo/index.tsx @@ -24,9 +24,9 @@ import {createStaticClick, SimpleInlineLinkText} from '#/components/Link' import {Loader} from '#/components/Loader' import {usePreemptivelyCompleteActivePolicyUpdate} from '#/components/PolicyUpdateOverlay/usePreemptivelyCompleteActivePolicyUpdate' import * as Toast from '#/components/Toast' +import {MIN_ACCESS_AGE} from '#/ageAssurance/const' import { isUnderAge, - MIN_ACCESS_AGE, useAgeAssuranceRegionConfigWithFallback, } from '#/ageAssurance/util' import {useAnalytics} from '#/analytics' diff --git a/src/state/birthdate.ts b/src/state/birthdate.ts index 71ca871df3..183e51aa45 100644 --- a/src/state/birthdate.ts +++ b/src/state/birthdate.ts @@ -1,10 +1,11 @@ import {useMemo} from 'react' import {useMutation, useQueryClient} from '@tanstack/react-query' +import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' import {preferencesQueryKey} from '#/state/queries/preferences' import {useAgent, useSession} from '#/state/session' import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance' -import {isUnderAge, maybeRestrictChatSettings} from '#/ageAssurance/util' +import {isUnderAge} from '#/ageAssurance/util' import {IS_DEV} from '#/env' import {account} from '#/storage' @@ -66,7 +67,11 @@ export function useBirthdateMutation() { }) if (isUnderAge(birthDate.toISOString(), 18)) { - maybeRestrictChatSettings({agent}) + await restrictChatSettings({ + agent, + restrictIncoming: true, + restrictGroupInvites: true, + }) } /** diff --git a/src/state/preferences/moderation-opts.tsx b/src/state/preferences/moderation-opts.tsx index b8592d77e0..119c9008db 100644 --- a/src/state/preferences/moderation-opts.tsx +++ b/src/state/preferences/moderation-opts.tsx @@ -2,7 +2,7 @@ import {createContext, useContext, useMemo} from 'react' import {AtpAgent, type ModerationOpts} from '@atproto/api' import {useHiddenPosts, useLabelDefinitions} from '#/state/preferences' -import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' +import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/const' import {useSession} from '#/state/session' import {usePreferencesQuery} from '../queries/preferences' diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx index 62f5772bef..0bc2f69997 100644 --- a/src/state/queries/messages/list-conversations.tsx +++ b/src/state/queries/messages/list-conversations.tsx @@ -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() diff --git a/src/state/queries/messages/restrictChatSettings.ts b/src/state/queries/messages/restrictChatSettings.ts index 3a1e64eff1..ad33385dc8 100644 --- a/src/state/queries/messages/restrictChatSettings.ts +++ b/src/state/queries/messages/restrictChatSettings.ts @@ -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 { + 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, diff --git a/src/state/queries/preferences/const.ts b/src/state/queries/preferences/const.ts index 7508fa4f6d..02f7d62a87 100644 --- a/src/state/queries/preferences/const.ts +++ b/src/state/queries/preferences/const.ts @@ -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, diff --git a/src/state/queries/preferences/moderation.ts b/src/state/queries/preferences/moderation.ts index 1c0f5a3fe5..c23e995e41 100644 --- a/src/state/queries/preferences/moderation.ts +++ b/src/state/queries/preferences/moderation.ts @@ -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, }: { diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index 0abc0ca6cf..fe669dfe8b 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -29,7 +29,6 @@ import { setCreatedAtForDid, } from '#/ageAssurance/data' import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state' -import {AgeAssuranceAccess} from '#/ageAssurance/types' import {features} from '#/analytics' import {emitNetworkConfirmed, emitNetworkLost} from '../events' import {addSessionErrorLog} from './logging' @@ -218,10 +217,14 @@ export async function createAgentAndCreateAccount( throw e }), // wait for AA data to load first, then check state - aa.then(async () => { - const {state} = unsafeGetAndComputeAgeAssurance({did: account.did}) - if (state.access !== AgeAssuranceAccess.Full) { - restrictChatSettings({agent, did: account.did}) + aa.then(() => { + const {flags} = unsafeGetAndComputeAgeAssurance({did: account.did}) + if (flags?.chatDisabled || flags?.groupChatDisabled) { + void restrictChatSettings({ + agent, + restrictIncoming: flags.chatDisabled, + restrictGroupInvites: flags.groupChatDisabled, + }) } }), ]).then(promises => {