[SDK] Add refreshSession and migrate the session-pinned infra (#11381)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Samuel Newman
2026-08-13 22:26:20 +03:00
committed by GitHub
parent 0c93d1e416
commit a4f2811f39
32 changed files with 676 additions and 268 deletions
@@ -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 = (
<>
<Trans>
We're having issues initializing the age assurance process for
your account. Please{' '}
<SimpleInlineLinkText
to={createSupportLink({code: SupportCode.AA_DID, email})}
label={_(msg`Contact support`)}>
contact support
</SimpleInlineLinkText>{' '}
for assistance.
</Trans>
</>
)
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 = (
<>
<Trans>
We're having issues initializing the age assurance process for
your account. Please{' '}
<SimpleInlineLinkText
to={createSupportLink({code: SupportCode.AA_DID, email})}
label={_(msg`Contact support`)}>
contact support
</SimpleInlineLinkText>{' '}
for assistance.
</Trans>
</>
)
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)
@@ -1,13 +1,15 @@
import {useMutation} from '@tanstack/react-query'
import {useAgent, useSession} from '#/state/session'
import {usePdsClient, useSession, useSessionApi} from '#/state/session'
import {com} from '#/lexicons'
export function useConfirmEmail({
onSuccess,
onError,
}: {onSuccess?: () => void; onError?: () => void} = {}) {
const agent = useAgent()
const pdsClient = usePdsClient()
const {currentAccount} = useSession()
const {refreshSession} = useSessionApi()
return useMutation({
mutationFn: async ({token}: {token: string}) => {
@@ -15,12 +17,12 @@ export function useConfirmEmail({
throw new Error('No email found for the current account')
}
await agent.com.atproto.server.confirmEmail({
await pdsClient.call(com.atproto.server.confirmEmail, {
email: currentAccount.email.trim(),
token: token.trim(),
})
// will update session state at root of app
await agent.resumeSession(agent.session!)
await refreshSession()
},
onSuccess,
onError,
@@ -1,10 +1,12 @@
import {useMutation} from '@tanstack/react-query'
import {useAgent, useSession} from '#/state/session'
import {usePdsClient, useSession, useSessionApi} from '#/state/session'
import {com} from '#/lexicons'
export function useManageEmail2FA() {
const agent = useAgent()
const pdsClient = usePdsClient()
const {currentAccount} = useSession()
const {refreshSession} = useSessionApi()
return useMutation({
mutationFn: async ({
@@ -17,13 +19,13 @@ export function useManageEmail2FA() {
throw new Error('No email found for the current account')
}
await agent.com.atproto.server.updateEmail({
await pdsClient.call(com.atproto.server.updateEmail, {
email: currentAccount.email,
emailAuthFactor: enabled,
token,
})
// will update session state at root of app
await agent.resumeSession(agent.session!)
await refreshSession()
},
})
}
@@ -1,19 +1,26 @@
import {type Client} from '@atproto/lex'
import {useMutation} from '@tanstack/react-query'
import {useAgent} from '#/state/session'
import {usePdsClient, useSessionApi} from '#/state/session'
import {useRequestEmailUpdate} from '#/components/dialogs/EmailDialog/data/useRequestEmailUpdate'
import {com} from '#/lexicons'
async function updateEmailAndRefreshSession(
agent: ReturnType<typeof useAgent>,
pdsClient: Client,
refreshSession: () => Promise<unknown>,
email: string,
token?: string,
) {
await agent.com.atproto.server.updateEmail({email: email.trim(), token})
await agent.resumeSession(agent.session!)
await pdsClient.call(com.atproto.server.updateEmail, {
email: email.trim(),
token,
})
await refreshSession()
}
export function useUpdateEmail() {
const agent = useAgent()
const pdsClient = usePdsClient()
const {refreshSession} = useSessionApi()
const {mutateAsync: requestEmailUpdate} = useRequestEmailUpdate()
return useMutation<
@@ -23,7 +30,12 @@ export function useUpdateEmail() {
>({
mutationFn: async ({email, token}: {email: string; token?: string}) => {
if (token) {
await updateEmailAndRefreshSession(agent, email, token)
await updateEmailAndRefreshSession(
pdsClient,
refreshSession,
email,
token,
)
return {
status: 'success',
}
@@ -34,7 +46,12 @@ export function useUpdateEmail() {
status: 'tokenRequired',
}
} else {
await updateEmailAndRefreshSession(agent, email, token)
await updateEmailAndRefreshSession(
pdsClient,
refreshSession,
email,
token,
)
return {
status: 'success',
}