replace the age assurance agent clone with a token-scoped client

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-08-04 01:14:39 +03:00
parent 88bc368991
commit 8d588518cb
2 changed files with 53 additions and 38 deletions
+19 -13
View File
@@ -1,5 +1,4 @@
import {Platform} from 'react-native' import {Platform} from 'react-native'
import {type AppBskyAgeassuranceBegin, AtpAgent} from '@atproto/api'
import {useMutation} from '@tanstack/react-query' import {useMutation} from '@tanstack/react-query'
import {wait} from '#/lib/async/wait' import {wait} from '#/lib/async/wait'
@@ -9,26 +8,28 @@ import {
PUBLIC_APPVIEW_DID, PUBLIC_APPVIEW_DID,
} from '#/lib/constants' } from '#/lib/constants'
import {isNetworkError} from '#/lib/hooks/useCleanError' import {isNetworkError} from '#/lib/hooks/useCleanError'
import {useAgent} from '#/state/session' import {createLexClient} from '#/lib/lexClient'
import {usePdsClient} from '#/state/session'
import {usePatchAgeAssuranceServerState} from '#/ageAssurance' import {usePatchAgeAssuranceServerState} from '#/ageAssurance'
import {logger} from '#/ageAssurance/logger' import {logger} from '#/ageAssurance/logger'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {BLUESKY_PROXY_DID} from '#/env' import {BLUESKY_PROXY_DID} from '#/env'
import {useGeolocation} from '#/geolocation' import {useGeolocation} from '#/geolocation'
import {app, com} from '#/lexicons'
const IS_DEV_ENV = BLUESKY_PROXY_DID !== PUBLIC_APPVIEW_DID const IS_DEV_ENV = BLUESKY_PROXY_DID !== PUBLIC_APPVIEW_DID
const APPVIEW = IS_DEV_ENV ? DEV_ENV_APPVIEW : PUBLIC_APPVIEW const APPVIEW = IS_DEV_ENV ? DEV_ENV_APPVIEW : PUBLIC_APPVIEW
export function useBeginAgeAssurance() { export function useBeginAgeAssurance() {
const ax = useAnalytics() const ax = useAnalytics()
const agent = useAgent() const pdsClient = usePdsClient()
const geolocation = useGeolocation() const geolocation = useGeolocation()
const patchAgeAssuranceStateResponse = usePatchAgeAssuranceServerState() const patchAgeAssuranceStateResponse = usePatchAgeAssuranceServerState()
return useMutation({ return useMutation({
async mutationFn( async mutationFn(
props: Omit< props: Omit<
AppBskyAgeassuranceBegin.InputSchema, app.bsky.ageassurance.begin.$InputBody,
'countryCode' | 'regionCode' 'countryCode' | 'regionCode'
>, >,
) { ) {
@@ -38,17 +39,22 @@ export function useBeginAgeAssurance() {
throw new Error(`Geolocation not available, cannot init age assurance.`) throw new Error(`Geolocation not available, cannot init age assurance.`)
} }
const { const {token} = await pdsClient.call(com.atproto.server.getServiceAuth, {
data: {token},
} = await agent.com.atproto.server.getServiceAuth({
aud: BLUESKY_PROXY_DID, aud: BLUESKY_PROXY_DID,
lxm: `app.bsky.ageassurance.begin`, lxm: `app.bsky.ageassurance.begin`,
}) })
const appView = new AtpAgent({service: APPVIEW}) /*
appView.sessionManager.session = {...agent.session!} * A single-use client scoped to the service-auth token: it has no session,
appView.sessionManager.session.accessJwt = token * so nothing can refresh it, and the request goes straight to the appview
appView.sessionManager.session.refreshJwt = '' * with the token as a static `authorization` header. A raw client is
* allowed to preset that header where a session-backed one is not, which
* also makes the old `refreshJwt = ''` clone unnecessary.
*/
const scopedClient = createLexClient({
service: APPVIEW,
headers: {authorization: `Bearer ${token}`},
})
ax.metric('ageAssurance:api:begin', { ax.metric('ageAssurance:api:begin', {
platform: Platform.OS, platform: Platform.OS,
@@ -60,9 +66,9 @@ export function useBeginAgeAssurance() {
* 2s wait is good actually. Email sending takes a hot sec and this helps * 2s wait is good actually. Email sending takes a hot sec and this helps
* ensure the email is ready for the user once they open their inbox. * ensure the email is ready for the user once they open their inbox.
*/ */
const {data} = await wait( const data = await wait(
2e3, 2e3,
appView.app.bsky.ageassurance.begin({ scopedClient.call(app.bsky.ageassurance.begin, {
...props, ...props,
countryCode, countryCode,
regionCode, regionCode,
@@ -1,6 +1,6 @@
import {useState} from 'react' import {useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {XRPCError} from '@atproto/api' import {XrpcResponseError} from '@atproto/lex'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -14,6 +14,7 @@ import {
import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
import {useTLDs} from '#/lib/hooks/useTLDs' import {useTLDs} from '#/lib/hooks/useTLDs'
import {isEmailMaybeInvalid} from '#/lib/strings/email' import {isEmailMaybeInvalid} from '#/lib/strings/email'
import {matchXrpcError} from '#/lib/xrpc-error'
import {type AppLanguage} from '#/locale/languages' import {type AppLanguage} from '#/locale/languages'
import {useLanguagePrefs} from '#/state/preferences' import {useLanguagePrefs} from '#/state/preferences'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
@@ -33,6 +34,7 @@ import {Text} from '#/components/Typography'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {useBeginAgeAssurance} from '#/ageAssurance/useBeginAgeAssurance' import {useBeginAgeAssurance} from '#/ageAssurance/useBeginAgeAssurance'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons'
export {useDialogControl} from '#/components/Dialog/context' export {useDialogControl} from '#/components/Dialog/context'
@@ -139,30 +141,37 @@ function Inner() {
msg`Something went wrong, please try again`, msg`Something went wrong, please try again`,
) )
if (e instanceof XRPCError) { if (e instanceof XrpcResponseError) {
if (e.error === 'InvalidEmail') { switch (matchXrpcError(e, app.bsky.ageassurance.begin)) {
error = _( case 'InvalidEmail':
msg`Please enter a valid, non-temporary email address. You may need to access this email in the future.`, error = _(
) msg`Please enter a valid, non-temporary email address. You may need to access this email in the future.`,
ax.metric('ageAssurance:initDialogError', {code: 'InvalidEmail'}) )
} else if (e.error === 'DidTooLong') { ax.metric('ageAssurance:initDialogError', {code: 'InvalidEmail'})
error = ( break
<> case 'DidTooLong':
<Trans> error = (
We're having issues initializing the age assurance process for <>
your account. Please{' '} <Trans>
<SimpleInlineLinkText We're having issues initializing the age assurance process for
to={createSupportLink({code: SupportCode.AA_DID, email})} your account. Please{' '}
label={_(msg`Contact support`)}> <SimpleInlineLinkText
contact support to={createSupportLink({code: SupportCode.AA_DID, email})}
</SimpleInlineLinkText>{' '} label={_(msg`Contact support`)}>
for assistance. contact support
</Trans> </SimpleInlineLinkText>{' '}
</> for assistance.
) </Trans>
ax.metric('ageAssurance:initDialogError', {code: 'DidTooLong'}) </>
} else { )
ax.metric('ageAssurance:initDialogError', {code: 'other'}) ax.metric('ageAssurance:initDialogError', {code: 'DidTooLong'})
break
default:
/*
* An undeclared code keeps the generic message rather than surfacing
* the server's text, as the old `e.error` fallthrough did.
*/
ax.metric('ageAssurance:initDialogError', {code: 'other'})
} }
} else { } else {
const {clean, raw} = cleanError(e) const {clean, raw} = cleanError(e)