diff --git a/src/ageAssurance/useBeginAgeAssurance.ts b/src/ageAssurance/useBeginAgeAssurance.ts index 28a4591747..e6e79e348d 100644 --- a/src/ageAssurance/useBeginAgeAssurance.ts +++ b/src/ageAssurance/useBeginAgeAssurance.ts @@ -1,5 +1,4 @@ import {Platform} from 'react-native' -import {type AppBskyAgeassuranceBegin, AtpAgent} from '@atproto/api' import {useMutation} from '@tanstack/react-query' import {wait} from '#/lib/async/wait' @@ -9,26 +8,28 @@ import { PUBLIC_APPVIEW_DID, } from '#/lib/constants' 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 {logger} from '#/ageAssurance/logger' import {useAnalytics} from '#/analytics' import {BLUESKY_PROXY_DID} from '#/env' import {useGeolocation} from '#/geolocation' +import {app, com} from '#/lexicons' const IS_DEV_ENV = BLUESKY_PROXY_DID !== PUBLIC_APPVIEW_DID const APPVIEW = IS_DEV_ENV ? DEV_ENV_APPVIEW : PUBLIC_APPVIEW export function useBeginAgeAssurance() { const ax = useAnalytics() - const agent = useAgent() + const pdsClient = usePdsClient() const geolocation = useGeolocation() const patchAgeAssuranceStateResponse = usePatchAgeAssuranceServerState() return useMutation({ async mutationFn( props: Omit< - AppBskyAgeassuranceBegin.InputSchema, + app.bsky.ageassurance.begin.$InputBody, 'countryCode' | 'regionCode' >, ) { @@ -38,17 +39,22 @@ export function useBeginAgeAssurance() { throw new Error(`Geolocation not available, cannot init age assurance.`) } - const { - data: {token}, - } = await agent.com.atproto.server.getServiceAuth({ + const {token} = await pdsClient.call(com.atproto.server.getServiceAuth, { aud: BLUESKY_PROXY_DID, lxm: `app.bsky.ageassurance.begin`, }) - const appView = new AtpAgent({service: APPVIEW}) - appView.sessionManager.session = {...agent.session!} - appView.sessionManager.session.accessJwt = token - appView.sessionManager.session.refreshJwt = '' + /* + * A single-use client scoped to the service-auth token: it has no session, + * so nothing can refresh it, and the request goes straight to the appview + * 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', { platform: Platform.OS, @@ -60,9 +66,9 @@ export function useBeginAgeAssurance() { * 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. */ - const {data} = await wait( + const data = await wait( 2e3, - appView.app.bsky.ageassurance.begin({ + scopedClient.call(app.bsky.ageassurance.begin, { ...props, countryCode, regionCode, diff --git a/src/components/ageAssurance/AgeAssuranceInitDialog.tsx b/src/components/ageAssurance/AgeAssuranceInitDialog.tsx index 9d74b0c6dc..052c3a4731 100644 --- a/src/components/ageAssurance/AgeAssuranceInitDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceInitDialog.tsx @@ -1,6 +1,6 @@ import {useState} from 'react' import {View} from 'react-native' -import {XRPCError} from '@atproto/api' +import {XrpcResponseError} from '@atproto/lex' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -14,6 +14,7 @@ import { import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo' import {useTLDs} from '#/lib/hooks/useTLDs' import {isEmailMaybeInvalid} from '#/lib/strings/email' +import {matchXrpcError} from '#/lib/xrpc-error' import {type AppLanguage} from '#/locale/languages' import {useLanguagePrefs} from '#/state/preferences' import {useSession} from '#/state/session' @@ -33,6 +34,7 @@ import {Text} from '#/components/Typography' import {useAgeAssurance} from '#/ageAssurance' import {useBeginAgeAssurance} from '#/ageAssurance/useBeginAgeAssurance' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' export {useDialogControl} from '#/components/Dialog/context' @@ -139,30 +141,37 @@ function Inner() { msg`Something went wrong, please try again`, ) - if (e instanceof XRPCError) { - if (e.error === 'InvalidEmail') { - 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') { - error = ( - <> - - We're having issues initializing the age assurance process for - your account. Please{' '} - - contact support - {' '} - for assistance. - - - ) - ax.metric('ageAssurance:initDialogError', {code: 'DidTooLong'}) - } else { - ax.metric('ageAssurance:initDialogError', {code: 'other'}) + if (e instanceof XrpcResponseError) { + switch (matchXrpcError(e, app.bsky.ageassurance.begin)) { + case 'InvalidEmail': + 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'}) + break + case 'DidTooLong': + error = ( + <> + + We're having issues initializing the age assurance process for + your account. Please{' '} + + contact support + {' '} + for assistance. + + + ) + 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 { const {clean, raw} = cleanError(e)