diff --git a/src/ageAssurance/components/RedirectOverlay.tsx b/src/ageAssurance/components/RedirectOverlay.tsx index 5a6f5b2934..5b31e94365 100644 --- a/src/ageAssurance/components/RedirectOverlay.tsx +++ b/src/ageAssurance/components/RedirectOverlay.tsx @@ -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 ( diff --git a/src/ageAssurance/data.tsx b/src/ageAssurance/data.tsx index 9cf6703dfc..2beac738c2 100644 --- a/src/ageAssurance/data.tsx +++ b/src/ageAssurance/data.tsx @@ -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(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( 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 { 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(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}), ]) } diff --git a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx index ad584ca203..acf20f38c5 100644 --- a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx @@ -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 ( diff --git a/src/state/queries/messages/actor-declaration.ts b/src/state/queries/messages/actor-declaration.ts index 17f36cbdd8..0824e52aae 100644 --- a/src/state/queries/messages/actor-declaration.ts +++ b/src/state/queries/messages/actor-declaration.ts @@ -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 } diff --git a/src/state/session/create-account.ts b/src/state/session/create-account.ts index 29dfabacfc..959df45b13 100644 --- a/src/state/session/create-account.ts +++ b/src/state/session/create-account.ts @@ -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[] = [ savePersonalDetails(pdsClient, birthDate), initializeProfile(pdsClient, {handle, createdAt, isProd}), diff --git a/src/state/session/session-core.ts b/src/state/session/session-core.ts index 5e3a9e8a8f..759e6afcfa 100644 --- a/src/state/session/session-core.ts +++ b/src/state/session/session-core.ts @@ -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())