migrate the age assurance data reads to the session clients
getOtherRequiredData's getPreferences read moves onto the sdk action, which also unblocks fetchActorDeclarationRecord - its only caller - so the record read moves to the pds client too. The prefetch and refetch entry points now take the transport they need rather than an agent: the appview client for getState and device signals, the account client for preferences and the declaration record. The session bundle still carries only an agent, so the three session call sites derive both clients from it; the full bundle rework is a later slice. The standalone config read stays on its own unauthenticated agent.
This commit is contained in:
@@ -16,7 +16,7 @@ import {Trans} from '@lingui/react/macro'
|
||||
import {retry} from '#/lib/async/retry'
|
||||
import {wait} from '#/lib/async/wait'
|
||||
import {parseLinkingUrl} from '#/lib/parseLinkingUrl'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, useSession} from '#/state/session'
|
||||
import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf'
|
||||
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
@@ -176,7 +176,7 @@ function Inner() {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const polling = useRef(false)
|
||||
const unmounted = useRef(false)
|
||||
const [error, setError] = useState(false)
|
||||
@@ -196,10 +196,10 @@ function Inner() {
|
||||
5,
|
||||
() => true,
|
||||
async () => {
|
||||
if (!agent.session) return
|
||||
if (!appviewClient.did) return
|
||||
if (unmounted.current) return
|
||||
|
||||
const data = await refetchAgeAssuranceServerState({agent})
|
||||
const data = await refetchAgeAssuranceServerState({appviewClient})
|
||||
|
||||
if (data?.state.status !== 'assured') {
|
||||
throw new Error(
|
||||
@@ -214,7 +214,7 @@ function Inner() {
|
||||
)
|
||||
.then(async data => {
|
||||
if (!data) return
|
||||
if (!agent.session) return
|
||||
if (!appviewClient.did) return
|
||||
if (unmounted.current) return
|
||||
|
||||
setSuccess(true)
|
||||
@@ -230,7 +230,7 @@ function Inner() {
|
||||
return () => {
|
||||
unmounted.current = true
|
||||
}
|
||||
}, [ax, agent])
|
||||
}, [ax, appviewClient])
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
|
||||
+65
-40
@@ -7,6 +7,8 @@ import {
|
||||
AtpAgent,
|
||||
type ChatBskyActorDeclaration,
|
||||
} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {getPreferences} from '@bsky.app/sdk'
|
||||
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
|
||||
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
|
||||
import {persistQueryClient} from '@tanstack/react-query-persist-client'
|
||||
@@ -21,7 +23,7 @@ import {
|
||||
snoozeBirthdateUpdateAllowedForDid,
|
||||
} from '#/state/birthdate'
|
||||
import {fetchActorDeclarationRecord} from '#/state/queries/messages/actor-declaration'
|
||||
import {useAgent, useSession} from '#/state/session'
|
||||
import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
|
||||
import {DEVICE_SIGNALS_SUPPORTED} from '#/ageAssurance/const'
|
||||
import * as debug from '#/ageAssurance/debug'
|
||||
import {logger} from '#/ageAssurance/logger'
|
||||
@@ -37,6 +39,7 @@ import {
|
||||
} from '#/ageAssurance/util'
|
||||
import {IS_DEV} from '#/env'
|
||||
import {useGeolocation} from '#/geolocation'
|
||||
import {app} from '#/lexicons'
|
||||
import {device} from '#/storage'
|
||||
|
||||
/**
|
||||
@@ -63,12 +66,6 @@ const [, cacheHydrationPromise] = persistQueryClient({
|
||||
persister,
|
||||
})
|
||||
|
||||
export function getDidFromAgentSession(agent: AtpAgent) {
|
||||
const sessionManager = agent.sessionManager
|
||||
if (!sessionManager || !sessionManager.did) return
|
||||
return sessionManager.did
|
||||
}
|
||||
|
||||
/*
|
||||
* Optimistic data
|
||||
*/
|
||||
@@ -187,7 +184,7 @@ export function useConfigQuery() {
|
||||
export function createServerStateQueryKey({did}: {did: string}) {
|
||||
return ['serverState', did]
|
||||
}
|
||||
export async function getServerState({agent}: {agent: AtpAgent}) {
|
||||
export async function getServerState({appviewClient}: {appviewClient: Client}) {
|
||||
if (debug.enabled && debug.serverState)
|
||||
return debug.resolve(debug.serverState)
|
||||
const geolocation = device.get(['mergedGeolocation'])
|
||||
@@ -195,17 +192,21 @@ export async function getServerState({agent}: {agent: AtpAgent}) {
|
||||
logger.error(`getServerState: missing geolocation countryCode`)
|
||||
return null
|
||||
}
|
||||
const {data} = await agent.app.bsky.ageassurance.getState({
|
||||
const data = await appviewClient.call(app.bsky.ageassurance.getState, {
|
||||
countryCode: geolocation.countryCode,
|
||||
regionCode: geolocation.regionCode,
|
||||
})
|
||||
const did = getDidFromAgentSession(agent)
|
||||
const did = appviewClient.did
|
||||
if (data && did && createdAtCache.has(did)) {
|
||||
/*
|
||||
* If account was just created, just use the local cache if available. On
|
||||
* subsequent reloads, the server should have the correct value.
|
||||
* subsequent reloads, the server should have the correct value. The cache
|
||||
* holds ISO datetime strings (written from `new Date().toISOString()`), so
|
||||
* assert the branded DatetimeString at this boundary.
|
||||
*/
|
||||
data.metadata.accountCreatedAt = createdAtCache.get(did)
|
||||
data.metadata.accountCreatedAt = createdAtCache.get(
|
||||
did,
|
||||
) as typeof data.metadata.accountCreatedAt
|
||||
}
|
||||
return data ?? null
|
||||
}
|
||||
@@ -218,8 +219,12 @@ export function getServerStateFromCache({
|
||||
createServerStateQueryKey({did}),
|
||||
)
|
||||
}
|
||||
export async function prefetchServerState({agent}: {agent: AtpAgent}) {
|
||||
const did = getDidFromAgentSession(agent)
|
||||
export async function prefetchServerState({
|
||||
appviewClient,
|
||||
}: {
|
||||
appviewClient: Client
|
||||
}) {
|
||||
const did = appviewClient.did
|
||||
|
||||
if (!did) return
|
||||
|
||||
@@ -234,7 +239,7 @@ export async function prefetchServerState({agent}: {agent: AtpAgent}) {
|
||||
|
||||
try {
|
||||
logger.debug(`prefetchServerState: resolving...`)
|
||||
const res = await networkRetry(3, () => getServerState({agent}))
|
||||
const res = await networkRetry(3, () => getServerState({appviewClient}))
|
||||
if (res) {
|
||||
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(qk, res)
|
||||
}
|
||||
@@ -245,11 +250,15 @@ export async function prefetchServerState({agent}: {agent: AtpAgent}) {
|
||||
})
|
||||
}
|
||||
}
|
||||
export async function refetchServerState({agent}: {agent: AtpAgent}) {
|
||||
const did = getDidFromAgentSession(agent)
|
||||
export async function refetchServerState({
|
||||
appviewClient,
|
||||
}: {
|
||||
appviewClient: Client
|
||||
}) {
|
||||
const did = appviewClient.did
|
||||
if (!did) return
|
||||
logger.debug(`refetchServerState: fetching...`)
|
||||
const res = await networkRetry(3, () => getServerState({agent}))
|
||||
const res = await networkRetry(3, () => getServerState({appviewClient}))
|
||||
if (res) {
|
||||
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(
|
||||
createServerStateQueryKey({did}),
|
||||
@@ -279,8 +288,8 @@ export function usePatchServerState() {
|
||||
)
|
||||
}
|
||||
export function useServerStateQuery() {
|
||||
const agent = useAgent()
|
||||
const did = getDidFromAgentSession(agent)
|
||||
const appviewClient = useAppviewClient()
|
||||
const did = appviewClient.did
|
||||
const query = useQuery(
|
||||
{
|
||||
enabled: !!did,
|
||||
@@ -290,7 +299,7 @@ export function useServerStateQuery() {
|
||||
},
|
||||
queryKey: createServerStateQueryKey({did: did!}),
|
||||
async queryFn() {
|
||||
return getServerState({agent})
|
||||
return getServerState({appviewClient})
|
||||
},
|
||||
},
|
||||
qc,
|
||||
@@ -342,15 +351,15 @@ export function createOtherRequiredDataQueryKey({did}: {did: string}) {
|
||||
return ['otherRequiredData', did]
|
||||
}
|
||||
async function getOtherRequiredData({
|
||||
agent,
|
||||
accountClient,
|
||||
}: {
|
||||
agent: AtpAgent
|
||||
accountClient: Client
|
||||
}): Promise<OtherRequiredData> {
|
||||
if (debug.enabled) return debug.resolve(debug.otherRequiredData)
|
||||
const did = getDidFromAgentSession(agent)
|
||||
const did = accountClient.did
|
||||
const [prefs, actorDeclaration] = await Promise.all([
|
||||
agent.getPreferences(),
|
||||
fetchActorDeclarationRecord({did, agent}),
|
||||
accountClient.call(getPreferences),
|
||||
fetchActorDeclarationRecord({did, client: accountClient}),
|
||||
])
|
||||
const data: OtherRequiredData = {
|
||||
birthdate: prefs.birthDate ? prefs.birthDate.toISOString() : undefined,
|
||||
@@ -426,8 +435,12 @@ export function setOtherRequiredDataActorDeclarationCache({
|
||||
next,
|
||||
)
|
||||
}
|
||||
export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) {
|
||||
const did = getDidFromAgentSession(agent)
|
||||
export async function prefetchOtherRequiredData({
|
||||
accountClient,
|
||||
}: {
|
||||
accountClient: Client
|
||||
}) {
|
||||
const did = accountClient.did
|
||||
|
||||
if (!did) return
|
||||
|
||||
@@ -442,7 +455,9 @@ export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) {
|
||||
|
||||
try {
|
||||
logger.debug(`prefetchOtherRequiredData: resolving...`)
|
||||
const res = await networkRetry(3, () => getOtherRequiredData({agent}))
|
||||
const res = await networkRetry(3, () =>
|
||||
getOtherRequiredData({accountClient}),
|
||||
)
|
||||
qc.setQueryData<OtherRequiredData>(qk, res)
|
||||
} catch (err) {
|
||||
const e = err as Error
|
||||
@@ -471,8 +486,8 @@ export function usePatchOtherRequiredData() {
|
||||
)
|
||||
}
|
||||
export function useOtherRequiredDataQuery() {
|
||||
const agent = useAgent()
|
||||
const did = getDidFromAgentSession(agent)
|
||||
const accountClient = usePdsClient()
|
||||
const did = accountClient.did
|
||||
return useQuery(
|
||||
{
|
||||
enabled: !!did,
|
||||
@@ -482,7 +497,7 @@ export function useOtherRequiredDataQuery() {
|
||||
},
|
||||
queryKey: createOtherRequiredDataQueryKey({did: did!}),
|
||||
async queryFn() {
|
||||
return getOtherRequiredData({agent})
|
||||
return getOtherRequiredData({accountClient})
|
||||
},
|
||||
},
|
||||
qc,
|
||||
@@ -577,8 +592,12 @@ export function setDeviceSignalsForRegion({
|
||||
prev => ({...prev, [regionKey]: signals}),
|
||||
)
|
||||
}
|
||||
export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) {
|
||||
const did = getDidFromAgentSession(agent)
|
||||
export async function prefetchDeviceSignals({
|
||||
appviewClient,
|
||||
}: {
|
||||
appviewClient: Client
|
||||
}) {
|
||||
const did = appviewClient.did
|
||||
if (!did) return
|
||||
|
||||
/**
|
||||
@@ -613,8 +632,8 @@ export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) {
|
||||
*/
|
||||
}
|
||||
export function useDeviceSignalsQuery() {
|
||||
const agent = useAgent()
|
||||
const did = getDidFromAgentSession(agent)
|
||||
const appviewClient = useAppviewClient()
|
||||
const did = appviewClient.did
|
||||
const {data: config} = useConfigQuery()
|
||||
const geolocation = useGeolocation()
|
||||
/*
|
||||
@@ -659,13 +678,19 @@ export function useDeviceSignalsQuery() {
|
||||
/**
|
||||
* Helper to prefetch all age assurance data from the server.
|
||||
*/
|
||||
export function prefetchAgeAssuranceServerData({agent}: {agent: AtpAgent}) {
|
||||
export function prefetchAgeAssuranceServerData({
|
||||
appviewClient,
|
||||
accountClient,
|
||||
}: {
|
||||
appviewClient: Client
|
||||
accountClient: Client
|
||||
}) {
|
||||
return Promise.allSettled([
|
||||
// config fetch initiated at the top of the App.platform.tsx files, awaited here
|
||||
configPrefetchPromise,
|
||||
prefetchServerState({agent}),
|
||||
prefetchOtherRequiredData({agent}),
|
||||
prefetchDeviceSignals({agent}),
|
||||
prefetchServerState({appviewClient}),
|
||||
prefetchOtherRequiredData({accountClient}),
|
||||
prefetchDeviceSignals({appviewClient}),
|
||||
])
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import {Trans} from '@lingui/react/macro'
|
||||
|
||||
import {retry} from '#/lib/async/retry'
|
||||
import {wait} from '#/lib/async/wait'
|
||||
import {useAgent} from '#/state/session'
|
||||
import {useAppviewClient} from '#/state/session'
|
||||
import {atoms as a, useTheme, web} from '#/alf'
|
||||
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
|
||||
import {Button, ButtonText} from '#/components/Button'
|
||||
@@ -84,7 +84,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const {_} = useLingui()
|
||||
const agent = useAgent()
|
||||
const appviewClient = useAppviewClient()
|
||||
const polling = useRef(false)
|
||||
const unmounted = useRef(false)
|
||||
const control = useAgeAssuranceRedirectDialogControl()
|
||||
@@ -104,10 +104,10 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
|
||||
5,
|
||||
() => true,
|
||||
async () => {
|
||||
if (!agent.session) return
|
||||
if (!appviewClient.did) return
|
||||
if (unmounted.current) return
|
||||
|
||||
const data = await refetchAgeAssuranceServerState({agent})
|
||||
const data = await refetchAgeAssuranceServerState({appviewClient})
|
||||
|
||||
if (data?.state.status !== 'assured') {
|
||||
throw new Error(
|
||||
@@ -122,7 +122,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
|
||||
)
|
||||
.then(async data => {
|
||||
if (!data) return
|
||||
if (!agent.session) return
|
||||
if (!appviewClient.did) return
|
||||
if (unmounted.current) return
|
||||
|
||||
setSuccess(true)
|
||||
@@ -138,7 +138,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
|
||||
return () => {
|
||||
unmounted.current = true
|
||||
}
|
||||
}, [ax, agent, control])
|
||||
}, [ax, appviewClient, control])
|
||||
|
||||
if (success) {
|
||||
return (
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import type AtpAgent from '@atproto/api'
|
||||
import {
|
||||
type AppBskyActorDefs,
|
||||
type ChatBskyActorDeclaration,
|
||||
} from '@atproto/api'
|
||||
import {type AppBskyActorDefs} from '@atproto/api'
|
||||
import {type Client} from '@atproto/lex'
|
||||
import {type DidString} from '@atproto/syntax'
|
||||
import {useMutation, useQueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {usePdsClient, useSession} from '#/state/session'
|
||||
import {resolveAllowGroupInvites} from '#/components/dms/util'
|
||||
import {com} from '#/lexicons'
|
||||
import {chat, com} from '#/lexicons'
|
||||
import {RQKEY as PROFILE_RKEY} from '../profile'
|
||||
|
||||
export function useUpdateActorDeclaration({
|
||||
@@ -119,25 +116,16 @@ export function useDeleteActorDeclaration() {
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Still takes the legacy agent: its only caller is `getOtherRequiredData` in
|
||||
* `#/ageAssurance/data`, which is blocked on `getPreferences` and so cannot
|
||||
* hand over a lex client yet.
|
||||
*/
|
||||
export async function fetchActorDeclarationRecord({
|
||||
agent,
|
||||
client,
|
||||
did,
|
||||
}: {
|
||||
agent: AtpAgent
|
||||
client: Client
|
||||
did?: string
|
||||
}) {
|
||||
if (!did) return
|
||||
const res = await agent.com.atproto.repo
|
||||
.getRecord({
|
||||
repo: did,
|
||||
collection: 'chat.bsky.actor.declaration',
|
||||
rkey: 'self',
|
||||
})
|
||||
const res = await client
|
||||
.get(chat.bsky.actor.declaration, {repo: did as DidString, rkey: 'self'})
|
||||
.catch(_e => undefined)
|
||||
return res?.data.value as ChatBskyActorDeclaration.Main
|
||||
return res?.value
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state'
|
||||
import {features} from '#/analytics'
|
||||
import {type app} from '#/lexicons'
|
||||
import {agentToPdsClient} from './clients'
|
||||
import {agentToAppviewClient, agentToPdsClient} from './clients'
|
||||
import {configureModerationForAccount} from './moderation'
|
||||
import {
|
||||
buildBundle,
|
||||
@@ -105,12 +105,15 @@ export async function createSessionBundleAndCreateAccount(
|
||||
setCreatedAtForDid({did: earlyAccount.did, createdAt})
|
||||
setBirthdateForDid({did: earlyAccount.did, birthdate})
|
||||
snoozeBirthdateUpdateAllowedForDid(earlyAccount.did)
|
||||
// Start the prefetch after seeding its synchronous birthdate inputs.
|
||||
const aa = prefetchAgeAssuranceServerData({agent: bundle.agent})
|
||||
|
||||
const isProd = Boolean(IS_PROD_SERVICE(service))
|
||||
// Post-signup writes all target the account's own repo and actor store.
|
||||
const pdsClient = agentToPdsClient(bundle.agent)
|
||||
// Start the prefetch after seeding its synchronous birthdate inputs.
|
||||
const aa = prefetchAgeAssuranceServerData({
|
||||
appviewClient: agentToAppviewClient(bundle.agent),
|
||||
accountClient: pdsClient,
|
||||
})
|
||||
|
||||
const isProd = Boolean(IS_PROD_SERVICE(service))
|
||||
const postSignupTasks: Promise<unknown>[] = [
|
||||
savePersonalDetails(pdsClient, birthDate),
|
||||
initializeProfile(pdsClient, {handle, createdAt, isProd}),
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
createPublicAgent,
|
||||
PasswordSessionManager,
|
||||
} from './bridge-agent'
|
||||
import {agentToAppviewClient, agentToPdsClient} from './clients'
|
||||
import {addSessionErrorLog} from './logging'
|
||||
import {configureModerationForAccount} from './moderation'
|
||||
import {networkAwareFetch} from './network'
|
||||
@@ -290,7 +291,10 @@ export async function createSessionBundleAndResume(
|
||||
) ?? storedAccount
|
||||
|
||||
configureModerationForAccount(bundle.agent, earlyAccount)
|
||||
const aa = prefetchAgeAssuranceServerData({agent: bundle.agent})
|
||||
const aa = prefetchAgeAssuranceServerData({
|
||||
appviewClient: agentToAppviewClient(bundle.agent),
|
||||
accountClient: agentToPdsClient(bundle.agent),
|
||||
})
|
||||
|
||||
/*
|
||||
* The proxy header is applied after the PDS-targeting setup above, so those
|
||||
@@ -355,7 +359,10 @@ export async function createSessionBundleAndLogin(
|
||||
|
||||
const gates = features.refresh({strategy: 'prefer-fresh-gates'})
|
||||
configureModerationForAccount(bundle.agent, earlyAccount)
|
||||
const aa = prefetchAgeAssuranceServerData({agent: bundle.agent})
|
||||
const aa = prefetchAgeAssuranceServerData({
|
||||
appviewClient: agentToAppviewClient(bundle.agent),
|
||||
accountClient: agentToPdsClient(bundle.agent),
|
||||
})
|
||||
|
||||
bundle.agent.configureProxy(BLUESKY_PROXY_HEADER.get())
|
||||
|
||||
|
||||
Reference in New Issue
Block a user