Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Write chat declaration record in response to different events (#10216)

Co-authored-by: Eric Bailey <git@esb.lol>

authored by

DS Boyce
Eric Bailey
and committed by
GitHub
8c2e4c6f d58ff894

+296 -123
+31 -4
src/ageAssurance/data.tsx
··· 4 4 type AppBskyAgeassuranceGetConfig, 5 5 type AppBskyAgeassuranceGetState, 6 6 AtpAgent, 7 + type ChatBskyActorDeclaration, 7 8 getAgeAssuranceRegionConfig, 8 9 } from '@atproto/api' 9 10 import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' ··· 19 20 hasSnoozedBirthdateUpdateForDid, 20 21 snoozeBirthdateUpdateAllowedForDid, 21 22 } from '#/state/birthdate' 23 + import {fetchActorDeclarationRecord} from '#/state/queries/messages/actor-declaration' 22 24 import {useAgent, useSession} from '#/state/session' 23 25 import * as debug from '#/ageAssurance/debug' 24 26 import {logger} from '#/ageAssurance/logger' ··· 53 55 persister, 54 56 }) 55 57 56 - function getDidFromAgentSession(agent: AtpAgent) { 58 + export function getDidFromAgentSession(agent: AtpAgent) { 57 59 const sessionManager = agent.sessionManager 58 60 if (!sessionManager || !sessionManager.did) return 59 61 return sessionManager.did ··· 329 331 330 332 export type OtherRequiredData = { 331 333 birthdate: string | undefined 334 + actorDeclaration?: ChatBskyActorDeclaration.Main 332 335 } 333 336 export function createOtherRequiredDataQueryKey({did}: {did: string}) { 334 337 return ['otherRequiredData', did] 335 338 } 336 - export async function getOtherRequiredData({ 339 + async function getOtherRequiredData({ 337 340 agent, 338 341 }: { 339 342 agent: AtpAgent 340 343 }): Promise<OtherRequiredData> { 341 344 if (debug.enabled) return debug.resolve(debug.otherRequiredData) 342 - const [prefs] = await Promise.all([agent.getPreferences()]) 345 + const did = getDidFromAgentSession(agent) 346 + const [prefs, actorDeclaration] = await Promise.all([ 347 + agent.getPreferences(), 348 + fetchActorDeclarationRecord({did, agent}), 349 + ]) 343 350 const data: OtherRequiredData = { 344 351 birthdate: prefs.birthDate ? prefs.birthDate.toISOString() : undefined, 352 + actorDeclaration, 345 353 } 346 354 347 355 /** ··· 359 367 } 360 368 } 361 369 362 - const did = getDidFromAgentSession(agent) 363 370 if (data && did && birthdateCache.has(did)) { 364 371 /* 365 372 * If birthdate was just set, use the local cache value. On subsequent ··· 392 399 }): OtherRequiredData | undefined { 393 400 return qc.getQueryData<OtherRequiredData>( 394 401 createOtherRequiredDataQueryKey({did}), 402 + ) 403 + } 404 + export function setOtherRequiredDataActorDeclarationCache({ 405 + did, 406 + actorDeclaration, 407 + }: { 408 + did: string 409 + actorDeclaration: ChatBskyActorDeclaration.Main 410 + }) { 411 + const prev = getOtherRequiredDataFromCache({did}) 412 + const next: OtherRequiredData = { 413 + birthdate: prev?.birthdate, 414 + actorDeclaration: { 415 + ...(prev?.actorDeclaration || {}), 416 + ...actorDeclaration, 417 + }, 418 + } 419 + qc.setQueryData<OtherRequiredData>( 420 + createOtherRequiredDataQueryKey({did}), 421 + next, 395 422 ) 396 423 } 397 424 export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) {
+9 -4
src/ageAssurance/index.tsx
··· 1 1 import {createContext, useCallback, useContext, useEffect, useMemo} from 'react' 2 2 3 3 import {useGetAndRegisterPushToken} from '#/lib/notifications/notifications' 4 + import {useAgent} from '#/state/session' 4 5 import {Provider as RedirectOverlayProvider} from '#/ageAssurance/components/RedirectOverlay' 5 6 import { 6 7 AgeAssuranceDataProvider, ··· 18 19 } from '#/ageAssurance/types' 19 20 import { 20 21 isUnderAge, 22 + maybeRestrictChatSettings, 21 23 MIN_ACCESS_AGE, 22 24 useAgeAssuranceRegionConfigWithFallback, 23 25 } from '#/ageAssurance/util' ··· 78 80 } 79 81 80 82 function InnerProvider({children}: {children: React.ReactNode}) { 83 + const agent = useAgent() 81 84 const state = useAgeAssuranceState() 82 85 const {data} = useAgeAssuranceDataContext() 83 86 const config = useAgeAssuranceRegionConfigWithFallback() ··· 85 88 86 89 const handleAccessUpdate = useCallback( 87 90 (s: AgeAssuranceState) => { 88 - void getAndRegisterPushToken({ 89 - isAgeRestricted: s.access !== AgeAssuranceAccess.Full, 90 - }) 91 + const isAgeRestricted = s.access !== AgeAssuranceAccess.Full 92 + if (isAgeRestricted) { 93 + void getAndRegisterPushToken({isAgeRestricted}) 94 + maybeRestrictChatSettings({agent}) 95 + } 91 96 }, 92 - [getAndRegisterPushToken], 97 + [agent, getAndRegisterPushToken], 93 98 ) 94 99 useOnAgeAssuranceAccessUpdate(handleAccessUpdate) 95 100
+138 -69
src/ageAssurance/state.ts
··· 1 1 import {useEffect, useMemo, useState} from 'react' 2 2 import {computeAgeAssuranceRegionAccess} from '@atproto/api' 3 3 4 + import {getAge} from '#/lib/strings/time' 4 5 import {useSession} from '#/state/session' 5 - import {useAgeAssuranceDataContext} from '#/ageAssurance/data' 6 + import { 7 + type AgeAssuranceData, 8 + getConfigFromCache, 9 + getOtherRequiredDataFromCache, 10 + getServerStateFromCache, 11 + useAgeAssuranceDataContext, 12 + } from '#/ageAssurance/data' 6 13 import {logger} from '#/ageAssurance/logger' 7 14 import { 8 15 AgeAssuranceAccess, ··· 12 19 parseStatusFromString, 13 20 } from '#/ageAssurance/types' 14 21 import {getAgeAssuranceRegionConfigWithFallback} from '#/ageAssurance/util' 15 - import {useGeolocation} from '#/geolocation' 22 + import {type Geolocation, useGeolocation} from '#/geolocation' 23 + import {device} from '#/storage' 16 24 17 - export function useAgeAssuranceState(): AgeAssuranceState { 18 - const {hasSession} = useSession() 19 - const geolocation = useGeolocation() 20 - const {config, state, data} = useAgeAssuranceDataContext() 25 + /** 26 + * Get final evaluated age assurance state. Handles fallbacks and defers to 27 + * server state before computing access based on AA config from the server + 28 + * geolocation and other data. 29 + */ 30 + export function computeAgeAssuranceState({ 31 + hasSession, 32 + config, 33 + geolocation, 34 + state, 35 + data, 36 + }: { 37 + hasSession: boolean 38 + config: AgeAssuranceData['config'] 39 + geolocation: Geolocation 40 + state: AgeAssuranceData['state'] 41 + data: AgeAssuranceData['data'] 42 + }) { 43 + /** 44 + * This is where we control logged-out moderation prefs. It's all 45 + * downstream of AA now. 46 + */ 47 + if (!hasSession) 48 + return { 49 + status: AgeAssuranceStatus.Unknown, 50 + access: AgeAssuranceAccess.Safe, 51 + } 21 52 22 - return useMemo(() => { 23 - /** 24 - * This is where we control logged-out moderation prefs. It's all 25 - * downstream of AA now. 26 - */ 27 - if (!hasSession) 28 - return { 29 - status: AgeAssuranceStatus.Unknown, 30 - access: AgeAssuranceAccess.Safe, 31 - } 53 + /** 54 + * This can happen if the prefetch fails (such as due to network issues). 55 + * The query handler will try it again, but if it continues to fail, of 56 + * course we won't have config. 57 + * 58 + * In this case, fail open to avoid blocking users. 59 + */ 60 + if (!config) { 61 + logger.warn('useAgeAssuranceState: missing config') 62 + return { 63 + status: AgeAssuranceStatus.Unknown, 64 + access: AgeAssuranceAccess.Safe, 65 + error: 'config' as const, 66 + } 67 + } 32 68 33 - /** 34 - * This can happen if the prefetch fails (such as due to network issues). 35 - * The query handler will try it again, but if it continues to fail, of 36 - * course we won't have config. 37 - * 38 - * In this case, fail open to avoid blocking users. 39 - */ 40 - if (!config) { 41 - logger.warn('useAgeAssuranceState: missing config') 42 - return { 43 - status: AgeAssuranceStatus.Unknown, 44 - access: AgeAssuranceAccess.Safe, 45 - error: 'config', 46 - } 69 + const region = getAgeAssuranceRegionConfigWithFallback(config, geolocation) 70 + const isAARequired = region.countryCode !== '*' 71 + const isTerminalState = 72 + state?.status === 'assured' || state?.status === 'blocked' 73 + 74 + /* 75 + * If we are in a terminal state and AA is required for this region, 76 + * we can trust the server state completely and avoid recomputing. 77 + */ 78 + if (isTerminalState && isAARequired) { 79 + return { 80 + lastInitiatedAt: state.lastInitiatedAt, 81 + status: parseStatusFromString(state.status), 82 + access: parseAccessFromString(state.access), 47 83 } 84 + } 48 85 49 - const region = getAgeAssuranceRegionConfigWithFallback(config, geolocation) 50 - const isAARequired = region.countryCode !== '*' 51 - const isTerminalState = 52 - state?.status === 'assured' || state?.status === 'blocked' 86 + /* 87 + * Otherwise, we need to compute the access based on the latest data. For 88 + * accounts with an accurate birthdate, our default fallback rules should 89 + * ensure correct access. 90 + */ 91 + const result = computeAgeAssuranceRegionAccess(region, data) 92 + const computed = { 93 + lastInitiatedAt: state?.lastInitiatedAt, 94 + // prefer server state 95 + status: state?.status 96 + ? parseStatusFromString(state?.status) 97 + : AgeAssuranceStatus.Unknown, 98 + // prefer server state 99 + access: result 100 + ? parseAccessFromString(result.access) 101 + : AgeAssuranceAccess.Full, 102 + } 103 + logger.debug('debug useAgeAssuranceState', { 104 + region, 105 + state, 106 + data, 107 + computed, 108 + }) 109 + return computed 110 + } 53 111 54 - /* 55 - * If we are in a terminal state and AA is required for this region, 56 - * we can trust the server state completely and avoid recomputing. 57 - */ 58 - if (isTerminalState && isAARequired) { 59 - return { 60 - lastInitiatedAt: state.lastInitiatedAt, 61 - status: parseStatusFromString(state.status), 62 - access: parseAccessFromString(state.access), 63 - } 112 + /** 113 + * This is a last-ditch helper for out-of-band reads of the AA state, such as 114 + * during account creation. Don't use it for anything else. 115 + */ 116 + export function getAndComputeAgeAssuranceState({did}: {did: string}) { 117 + const config = getConfigFromCache() 118 + const state = getServerStateFromCache({did}) 119 + const data = getOtherRequiredDataFromCache({did}) 120 + const geolocation = device.get(['mergedGeolocation']) 121 + 122 + if (!geolocation || !config || !state || !data) { 123 + return { 124 + status: AgeAssuranceStatus.Unknown, 125 + access: AgeAssuranceAccess.Safe, 64 126 } 127 + } 65 128 66 - /* 67 - * Otherwise, we need to compute the access based on the latest data. For 68 - * accounts with an accurate birthdate, our default fallback rules should 69 - * ensure correct access. 70 - */ 71 - const result = computeAgeAssuranceRegionAccess(region, data) 72 - const computed = { 73 - lastInitiatedAt: state?.lastInitiatedAt, 74 - // prefer server state 75 - status: state?.status 76 - ? parseStatusFromString(state?.status) 77 - : AgeAssuranceStatus.Unknown, 78 - // prefer server state 79 - access: result 80 - ? parseAccessFromString(result.access) 81 - : AgeAssuranceAccess.Full, 82 - } 83 - logger.debug('debug useAgeAssuranceState', { 84 - region, 85 - state, 86 - data, 87 - computed, 88 - }) 89 - return computed 90 - }, [hasSession, geolocation, config, state, data]) 129 + return computeAgeAssuranceState({ 130 + hasSession: true, 131 + config, 132 + geolocation, 133 + state: state.state, 134 + data: { 135 + accountCreatedAt: state.metadata?.accountCreatedAt, 136 + declaredAge: data?.birthdate 137 + ? getAge(new Date(data.birthdate)) 138 + : undefined, 139 + birthdate: data?.birthdate, 140 + }, 141 + }) 142 + } 143 + 144 + export function useAgeAssuranceState(): AgeAssuranceState { 145 + const {hasSession} = useSession() 146 + const geolocation = useGeolocation() 147 + const {config, state, data} = useAgeAssuranceDataContext() 148 + 149 + return useMemo( 150 + () => 151 + computeAgeAssuranceState({ 152 + hasSession, 153 + config, 154 + geolocation, 155 + state, 156 + data, 157 + }), 158 + [hasSession, geolocation, config, state, data], 159 + ) 91 160 } 92 161 93 162 export function useOnAgeAssuranceAccessUpdate(
+20 -1
src/ageAssurance/util.ts
··· 2 2 import { 3 3 ageAssuranceRuleIDs as ids, 4 4 type AppBskyAgeassuranceDefs, 5 + type AtpAgent, 5 6 getAgeAssuranceRegionConfig, 6 7 type ModerationPrefs, 7 8 } from '@atproto/api' 8 9 9 10 import {getAge} from '#/lib/strings/time' 11 + import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' 10 12 import {DEFAULT_LOGGED_OUT_LABEL_PREFERENCES} from '#/state/queries/preferences/moderation' 11 - import {useAgeAssuranceDataContext} from '#/ageAssurance/data' 13 + import { 14 + getDidFromAgentSession, 15 + getOtherRequiredDataFromCache, 16 + useAgeAssuranceDataContext, 17 + } from '#/ageAssurance/data' 12 18 import {AgeAssuranceAccess} from '#/ageAssurance/types' 13 19 import {type Geolocation, useGeolocation} from '#/geolocation' 14 20 ··· 109 115 adultContentEnabled: false, 110 116 labels: DEFAULT_LOGGED_OUT_LABEL_PREFERENCES, 111 117 }) 118 + 119 + /** 120 + * Checks our cache of the actor's chat declaration record, and if it's not 121 + * already restricted, restricts it. 122 + */ 123 + export function maybeRestrictChatSettings({agent}: {agent: AtpAgent}) { 124 + const did = getDidFromAgentSession(agent) 125 + if (!did) return 126 + const data = getOtherRequiredDataFromCache({did}) 127 + // ...update the chat setting record if allowIncoming is not already 'none'. 128 + if (data?.actorDeclaration?.allowIncoming === 'none') return 129 + restrictChatSettings({agent, did}) 130 + }
+17 -23
src/components/dialogs/BirthDateSettings.tsx
··· 1 1 import {useCallback, useMemo, useState} from 'react' 2 2 import {View} from 'react-native' 3 - import {msg} from '@lingui/core/macro' 4 - import {useLingui} from '@lingui/react' 5 - import {Trans} from '@lingui/react/macro' 3 + import {Trans, useLingui} from '@lingui/react/macro' 6 4 7 5 import {useCleanError} from '#/lib/hooks/useCleanError' 8 6 import {isAppPassword} from '#/lib/jwt' ··· 34 32 control: Dialog.DialogControlProps 35 33 }) { 36 34 const t = useTheme() 37 - const {_} = useLingui() 35 + const {t: l} = useLingui() 38 36 const {isLoading, error, data: preferences} = usePreferencesQuery() 39 37 const isBirthdateUpdateAllowed = useIsBirthdateUpdateAllowed() 40 38 const {currentAccount} = useSession() ··· 45 43 <Dialog.Handle /> 46 44 {isBirthdateUpdateAllowed ? ( 47 45 <Dialog.ScrollableInner 48 - label={_(msg`My Birthdate`)} 46 + label={l`My birthdate`} 49 47 style={web({maxWidth: 400})}> 50 48 <View style={[a.gap_md]}> 51 49 <Text style={[a.text_xl, a.font_semi_bold]}> 52 - <Trans>My Birthdate</Trans> 50 + <Trans>My birthdate</Trans> 53 51 </Text> 54 52 <Text 55 53 style={[a.text_md, a.leading_snug, t.atoms.text_contrast_medium]}> ··· 64 62 <ErrorMessage 65 63 message={ 66 64 error?.toString() || 67 - _( 68 - msg`We were unable to load your birthdate preferences. Please try again.`, 69 - ) 65 + l`We were unable to load your birthdate preferences. Please try again.` 70 66 } 71 67 style={[a.rounded_sm]} 72 68 /> ··· 88 84 </Dialog.ScrollableInner> 89 85 ) : ( 90 86 <Dialog.ScrollableInner 91 - label={_(msg`You recently changed your birthdate`)} 87 + label={l`You recently changed your birthdate`} 92 88 style={web({maxWidth: 400})}> 93 89 <View style={[a.gap_sm]}> 94 90 <Text ··· 123 119 control: Dialog.DialogControlProps 124 120 preferences: UsePreferencesQueryResponse 125 121 }) { 126 - const {_} = useLingui() 122 + const {t: l} = useLingui() 127 123 const cleanError = useCleanError() 128 124 const [date, setDate] = useState(preferences.birthDate || getDateAgo(18)) 129 125 const {isPending, error, mutateAsync: setBirthDate} = useBirthdateMutation() 130 126 const hasChanged = date !== preferences.birthDate 131 127 const errorMessage = useMemo(() => { 132 128 if (error) { 133 - const {raw, clean} = cleanError(error) 134 - return clean || raw || error.toString() 129 + const e = error as Error 130 + const {raw, clean} = cleanError(e) 131 + return clean || raw || e.toString() 135 132 } 136 133 }, [error, cleanError]) 137 134 ··· 146 143 await setBirthDate({birthDate: date}) 147 144 } 148 145 control.close() 149 - } catch (e: any) { 146 + } catch (error) { 147 + const e = error as Error 150 148 logger.error(`setBirthDate failed`, {message: e.message}) 151 149 } 152 150 }, [date, setBirthDate, control, hasChanged]) ··· 158 156 testID="birthdayInput" 159 157 value={date} 160 158 onChangeDate={newDate => setDate(new Date(newDate))} 161 - label={_(msg`Birthdate`)} 162 - accessibilityHint={_(msg`Enter your birthdate`)} 159 + label={l`Birthdate`} 160 + accessibilityHint={l`Enter your birthdate`} 163 161 /> 164 162 </View> 165 - 166 163 {isUnder18 && hasChanged && ( 167 164 <Admonition type="info"> 168 165 <Trans> ··· 171 168 </Trans> 172 169 </Admonition> 173 170 )} 174 - 175 171 {isUnder13 && ( 176 172 <Admonition type="error"> 177 173 <Trans> 178 174 You must be at least 13 years old to use Bluesky. Read our{' '} 179 175 <SimpleInlineLinkText 180 176 to="https://bsky.social/about/support/tos" 181 - label={_(msg`Terms of Service`)}> 177 + label={l`Terms of Service`}> 182 178 Terms of Service 183 179 </SimpleInlineLinkText>{' '} 184 180 for more information. 185 181 </Trans> 186 182 </Admonition> 187 183 )} 188 - 189 184 {errorMessage ? ( 190 185 <ErrorMessage message={errorMessage} style={[a.rounded_sm]} /> 191 186 ) : undefined} 192 - 193 187 <View style={IS_WEB && [a.flex_row, a.justify_end]}> 194 188 <Button 195 - label={hasChanged ? _(msg`Save birthdate`) : _(msg`Done`)} 189 + label={hasChanged ? l`Save birthdate` : l`Done`} 196 190 size="large" 197 - onPress={onSave} 191 + onPress={() => void onSave()} 198 192 variant="solid" 199 193 color="primary" 200 194 disabled={isUnder13}>
+6
src/state/birthdate.ts
··· 4 4 import {preferencesQueryKey} from '#/state/queries/preferences' 5 5 import {useAgent, useSession} from '#/state/session' 6 6 import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance' 7 + import {isUnderAge, maybeRestrictChatSettings} from '#/ageAssurance/util' 7 8 import {IS_DEV} from '#/env' 8 9 import {account} from '#/storage' 9 10 ··· 63 64 await queryClient.invalidateQueries({ 64 65 queryKey: preferencesQueryKey, 65 66 }) 67 + 68 + if (isUnderAge(birthDate.toISOString(), 18)) { 69 + maybeRestrictChatSettings({agent}) 70 + } 71 + 66 72 /** 67 73 * Also patch the age assurance other required data with the new 68 74 * birthdate, which may change the user's age assurance access level.
+23 -1
src/state/queries/messages/actor-declaration.ts
··· 1 - import {type AppBskyActorDefs} from '@atproto/api' 1 + import type AtpAgent from '@atproto/api' 2 + import { 3 + type AppBskyActorDefs, 4 + type ChatBskyActorDeclaration, 5 + } from '@atproto/api' 2 6 import {useMutation, useQueryClient} from '@tanstack/react-query' 3 7 4 8 import {logger} from '#/logger' ··· 78 82 }, 79 83 }) 80 84 } 85 + 86 + export async function fetchActorDeclarationRecord({ 87 + agent, 88 + did, 89 + }: { 90 + agent: AtpAgent 91 + did?: string 92 + }) { 93 + if (!did) return 94 + const res = await agent.com.atproto.repo 95 + .getRecord({ 96 + repo: did, 97 + collection: 'chat.bsky.actor.declaration', 98 + rkey: 'self', 99 + }) 100 + .catch(_e => undefined) 101 + return res?.data.value as ChatBskyActorDeclaration.Main 102 + }
+39
src/state/queries/messages/restrictChatSettings.ts
··· 1 + import type AtpAgent from '@atproto/api' 2 + import {type ChatBskyActorDeclaration} from '@atproto/api' 3 + 4 + import {networkRetry} from '#/lib/async/retry' 5 + import {logger} from '#/logger' 6 + import {setOtherRequiredDataActorDeclarationCache} from '#/ageAssurance/data' 7 + 8 + /** 9 + * Helper to update the chat settings record. 10 + */ 11 + export async function restrictChatSettings({ 12 + agent, 13 + did, 14 + }: { 15 + agent: AtpAgent 16 + did: string 17 + }): Promise<void> { 18 + try { 19 + const record: ChatBskyActorDeclaration.Main = { 20 + $type: 'chat.bsky.actor.declaration', 21 + allowIncoming: 'none', 22 + } 23 + await networkRetry(3, () => 24 + agent.com.atproto.repo.putRecord({ 25 + repo: did, 26 + collection: 'chat.bsky.actor.declaration', 27 + rkey: 'self', 28 + record, 29 + }), 30 + ) 31 + // important, update local cache to avoid running this again 32 + setOtherRequiredDataActorDeclarationCache({ 33 + did, 34 + actorDeclaration: record, 35 + }) 36 + } catch { 37 + logger.error(`restrictChatSettings: failed to set chat declaration`) 38 + } 39 + }
+3
src/state/session/__tests__/session-test.ts
··· 12 12 13 13 jest.mock('../../birthdate') 14 14 jest.mock('../../../ageAssurance/data') 15 + jest.mock('../../../ageAssurance/state', () => ({ 16 + getAndComputeAgeAssuranceState: () => ({}), 17 + })) 15 18 jest.mock('#/lib/notifications/notifications', () => ({ 16 19 unregisterPushToken(_agents: BskyAgent[]) { 17 20 return Promise.resolve()
+10 -21
src/state/session/agent.ts
··· 22 22 PUBLIC_BSKY_SERVICE, 23 23 TIMELINE_SAVED_FEED, 24 24 } from '#/lib/constants' 25 - import {getAge} from '#/lib/strings/time' 26 25 import {logger} from '#/logger' 27 26 import {snoozeBirthdateUpdateAllowedForDid} from '#/state/birthdate' 27 + import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' 28 28 import {snoozeEmailConfirmationPrompt} from '#/state/shell/reminders' 29 29 import { 30 30 prefetchAgeAssuranceData, 31 31 setBirthdateForDid, 32 32 setCreatedAtForDid, 33 33 } from '#/ageAssurance/data' 34 + import {getAndComputeAgeAssuranceState} from '#/ageAssurance/state' 35 + import {AgeAssuranceAccess} from '#/ageAssurance/types' 34 36 import {features} from '#/analytics' 35 37 import {emitNetworkConfirmed, emitNetworkLost} from '../events' 36 38 import {addSessionErrorLog} from './logging' ··· 218 220 logger.info(`createAgentAndCreateAccount: failed to set initial feeds`) 219 221 throw e 220 222 }), 221 - ...(getAge(birthDate) < 18 222 - ? [ 223 - networkRetry(3, () => { 224 - return agent.com.atproto.repo.putRecord({ 225 - repo: account.did, 226 - collection: 'chat.bsky.actor.declaration', 227 - rkey: 'self', 228 - record: { 229 - $type: 'chat.bsky.actor.declaration', 230 - allowIncoming: 'none', 231 - }, 232 - }) 233 - }).catch(e => { 234 - logger.info( 235 - `createAgentAndCreateAccount: failed to set chat declaration`, 236 - ) 237 - throw e 238 - }), 239 - ] 240 - : []), 223 + // wait for AA data to load first, then check state 224 + aa.then(async () => { 225 + const state = getAndComputeAgeAssuranceState({did: account.did}) 226 + if (state.access !== AgeAssuranceAccess.Full) { 227 + restrictChatSettings({agent, did: account.did}) 228 + } 229 + }), 241 230 ]).then(promises => { 242 231 const rejected = promises.filter(p => p.status === 'rejected') 243 232 if (rejected.length > 0) {