From ed652ad484963c133340fe0171e991285ca0fb41 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 3 Aug 2026 23:17:03 +0300 Subject: [PATCH] migrate the verification and chat declaration records to the pds client Co-Authored-By: Claude Fable 5 --- src/ageAssurance/data.tsx | 12 +++++++ src/state/birthdate.ts | 5 +-- .../queries/messages/restrictChatSettings.ts | 23 ++++++------ .../useVerificationCreateMutation.tsx | 35 +++++++++++-------- .../useVerificationsRemoveMutation.tsx | 25 ++++++------- src/state/session/create-account.ts | 12 +++++-- 6 files changed, 71 insertions(+), 41 deletions(-) diff --git a/src/ageAssurance/data.tsx b/src/ageAssurance/data.tsx index 9cf6703dfc..eee50ba74e 100644 --- a/src/ageAssurance/data.tsx +++ b/src/ageAssurance/data.tsx @@ -7,6 +7,7 @@ import { AtpAgent, type ChatBskyActorDeclaration, } from '@atproto/api' +import {type Client} from '@atproto/lex' import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' import {focusManager, QueryClient, useQuery} from '@tanstack/react-query' import {persistQueryClient} from '@tanstack/react-query-persist-client' @@ -69,6 +70,17 @@ export function getDidFromAgentSession(agent: AtpAgent) { return sessionManager.did } +/** + * Reads the active account did from a lex {@link Client}. Logged-out clients + * expose `did: undefined`, so this returns undefined in that case. + * + * Lives alongside {@link getDidFromAgentSession} rather than replacing it: the + * rest of this file is still agent-shaped and blocked on `getPreferences`. + */ +export function getDidFromClient(client: Client) { + return client.did +} + /* * Optimistic data */ diff --git a/src/state/birthdate.ts b/src/state/birthdate.ts index 183e51aa45..c405febe4e 100644 --- a/src/state/birthdate.ts +++ b/src/state/birthdate.ts @@ -3,7 +3,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query' import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' import {preferencesQueryKey} from '#/state/queries/preferences' -import {useAgent, useSession} from '#/state/session' +import {useAgent, usePdsClient, useSession} from '#/state/session' import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance' import {isUnderAge} from '#/ageAssurance/util' import {IS_DEV} from '#/env' @@ -55,6 +55,7 @@ export function useIsBirthdateUpdateAllowed() { export function useBirthdateMutation() { const queryClient = useQueryClient() const agent = useAgent() + const pdsClient = usePdsClient() const patchOtherRequiredData = usePatchAgeAssuranceOtherRequiredData() return useMutation({ @@ -68,7 +69,7 @@ export function useBirthdateMutation() { if (isUnderAge(birthDate.toISOString(), 18)) { await restrictChatSettings({ - agent, + client: pdsClient, restrictIncoming: true, restrictGroupInvites: true, }) diff --git a/src/state/queries/messages/restrictChatSettings.ts b/src/state/queries/messages/restrictChatSettings.ts index ad33385dc8..915fcf6738 100644 --- a/src/state/queries/messages/restrictChatSettings.ts +++ b/src/state/queries/messages/restrictChatSettings.ts @@ -1,13 +1,13 @@ -import type AtpAgent from '@atproto/api' -import {type ChatBskyActorDeclaration} from '@atproto/api' +import {type Client} from '@atproto/lex' import {networkRetry} from '#/lib/async/retry' import {logger} from '#/logger' import { - getDidFromAgentSession, + getDidFromClient, getOtherRequiredDataFromCache, setOtherRequiredDataActorDeclarationCache, } from '#/ageAssurance/data' +import {chat} from '#/lexicons' /** * Updates the chat actor declaration record to restrict who can contact the @@ -24,15 +24,15 @@ import { * back to the lexicon defaults when the cache is empty. */ export async function restrictChatSettings({ - agent, + client, restrictIncoming = false, restrictGroupInvites = false, }: { - agent: AtpAgent + client: Client restrictIncoming?: boolean restrictGroupInvites?: boolean }): Promise { - const did = getDidFromAgentSession(agent) + const did = getDidFromClient(client) if (!did) return const cached = getOtherRequiredDataFromCache({did})?.actorDeclaration @@ -49,7 +49,7 @@ export async function restrictChatSettings({ ) } - const record: ChatBskyActorDeclaration.Main = { + const record: chat.bsky.actor.declaration.Main = { $type: 'chat.bsky.actor.declaration', allowIncoming: restrictIncoming ? 'none' @@ -69,11 +69,14 @@ export async function restrictChatSettings({ try { await networkRetry(3, () => - agent.com.atproto.repo.putRecord({ + /* + * A record helper, not a raw `com.atproto.repo.putRecord`: lex forces + * `service: null` on record helpers, so the write lands on the account's + * PDS even though the collection is `chat.bsky.*`. + */ + client.put(chat.bsky.actor.declaration, record, { repo: did, - collection: 'chat.bsky.actor.declaration', rkey: 'self', - record, }), ) // important, update local cache to avoid running this again diff --git a/src/state/queries/verification/useVerificationCreateMutation.tsx b/src/state/queries/verification/useVerificationCreateMutation.tsx index 083a6f3b54..4f1a318f48 100644 --- a/src/state/queries/verification/useVerificationCreateMutation.tsx +++ b/src/state/queries/verification/useVerificationCreateMutation.tsx @@ -1,15 +1,22 @@ -import {type AppBskyActorGetProfile} from '@atproto/api' +import { + type AtIdentifierString, + type DidString, + type HandleString, + toDatetimeString, +} from '@atproto/syntax' import {useMutation} from '@tanstack/react-query' import {until} from '#/lib/async/until' import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' import type * as bsky from '#/types/bsky' export function useVerificationCreateMutation() { const ax = useAnalytics() - const agent = useAgent() + const appviewClient = useAppviewClient() + const pdsClient = usePdsClient() const {currentAccount} = useSession() const updateProfileVerificationCache = useUpdateProfileVerificationCache() @@ -19,20 +26,18 @@ export function useVerificationCreateMutation() { throw new Error('User not logged in') } - const {uri} = await agent.app.bsky.graph.verification.create( - {repo: currentAccount.did}, - { - subject: profile.did, - createdAt: new Date().toISOString(), - handle: profile.handle, - displayName: profile.displayName || '', - }, - ) + const {uri} = await pdsClient.create(app.bsky.graph.verification, { + // the profile view is still legacy-typed, so its strings are unbranded + subject: profile.did as DidString, + createdAt: toDatetimeString(new Date()), + handle: profile.handle as HandleString, + displayName: profile.displayName || '', + }) await until( 5, 1e3, - ({data: profile}: AppBskyActorGetProfile.Response) => { + (profile: app.bsky.actor.getProfile.$OutputBody) => { if ( profile.verification && profile.verification.verifications.find(v => v.uri === uri) @@ -42,7 +47,9 @@ export function useVerificationCreateMutation() { return false }, () => { - return agent.getProfile({actor: profile.did ?? ''}) + return appviewClient.call(app.bsky.actor.getProfile, { + actor: (profile.did ?? '') as AtIdentifierString, + }) }, ) }, diff --git a/src/state/queries/verification/useVerificationsRemoveMutation.tsx b/src/state/queries/verification/useVerificationsRemoveMutation.tsx index 3fa95497fd..657c50c909 100644 --- a/src/state/queries/verification/useVerificationsRemoveMutation.tsx +++ b/src/state/queries/verification/useVerificationsRemoveMutation.tsx @@ -1,19 +1,18 @@ -import { - type AppBskyActorDefs, - type AppBskyActorGetProfile, - AtUri, -} from '@atproto/api' +import {type AppBskyActorDefs} from '@atproto/api' +import {type AtIdentifierString, AtUri} from '@atproto/syntax' import {useMutation} from '@tanstack/react-query' import {until} from '#/lib/async/until' import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' import type * as bsky from '#/types/bsky' export function useVerificationsRemoveMutation() { const ax = useAnalytics() - const agent = useAgent() + const appviewClient = useAppviewClient() + const pdsClient = usePdsClient() const {currentAccount} = useSession() const updateProfileVerificationCache = useUpdateProfileVerificationCache() @@ -33,9 +32,8 @@ export function useVerificationsRemoveMutation() { await Promise.all( uris.map(uri => { - return agent.app.bsky.graph.verification.delete({ - repo: currentAccount.did, - rkey: new AtUri(uri).rkey, + return pdsClient.delete(app.bsky.graph.verification, { + rkey: new AtUri(uri).rkeySafe, }) }), ) @@ -43,7 +41,7 @@ export function useVerificationsRemoveMutation() { await until( 5, 1e3, - ({data: profile}: AppBskyActorGetProfile.Response) => { + (profile: app.bsky.actor.getProfile.$OutputBody) => { if ( !profile.verification?.verifications.some(v => uris.includes(v.uri)) ) { @@ -52,7 +50,10 @@ export function useVerificationsRemoveMutation() { return false }, () => { - return agent.getProfile({actor: profile.did ?? ''}) + return appviewClient.call(app.bsky.actor.getProfile, { + // the profile view is still legacy-typed, so its did is unbranded + actor: (profile.did ?? '') as AtIdentifierString, + }) }, ) }, diff --git a/src/state/session/create-account.ts b/src/state/session/create-account.ts index fd67681482..b44bed4dbd 100644 --- a/src/state/session/create-account.ts +++ b/src/state/session/create-account.ts @@ -1,5 +1,6 @@ import {type AppBskyActorProfile, type Un$Typed} from '@atproto/api' import {TID} from '@atproto/common-web' +import {type Client} from '@atproto/lex' import {PasswordSession} from '@atproto/lex-password-session' import {networkRetry} from '#/lib/async/retry' @@ -21,6 +22,7 @@ import { import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state' import {features} from '#/analytics' import {type BskyAppAgent} from './bridge-agent' +import {agentToPdsClient} from './clients' import {configureModerationForAccount} from './moderation' import { buildBundle, @@ -109,7 +111,11 @@ export async function createSessionBundleAndCreateAccount( if (isProd) { postSignupTasks.push( initializeSavedFeeds(bundle.agent), - restrictChatAfterAgeAssurance(aa, bundle.agent, earlyAccount.did), + restrictChatAfterAgeAssurance( + aa, + agentToPdsClient(bundle.agent), + earlyAccount.did, + ), ) } // Post-signup writes are not required to enter onboarding. @@ -207,14 +213,14 @@ function initializeSavedFeeds(agent: BskyAppAgent) { function restrictChatAfterAgeAssurance( ageAssurance: Promise, - agent: BskyAppAgent, + client: Client, did: string, ) { return ageAssurance.then(() => { const {flags} = unsafeGetAndComputeAgeAssurance({did}) if (flags?.chatDisabled || flags?.groupChatDisabled) { void restrictChatSettings({ - agent, + client, restrictIncoming: flags.chatDisabled, restrictGroupInvites: flags.groupChatDisabled, })