[SDK] Migrate agent sugar to @bsky.app/sdk actions (#11382)

This commit is contained in:
Samuel Newman
2026-08-13 22:26:21 +03:00
committed by GitHub
parent a4f2811f39
commit dc31e7fd12
24 changed files with 520 additions and 389 deletions
@@ -16,7 +16,7 @@ import {Trans} from '@lingui/react/macro'
import {retry} from '#/lib/async/retry' import {retry} from '#/lib/async/retry'
import {wait} from '#/lib/async/wait' import {wait} from '#/lib/async/wait'
import {parseLinkingUrl} from '#/lib/parseLinkingUrl' 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 {atoms as a, platform, useBreakpoints, useTheme} from '#/alf'
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
@@ -176,7 +176,7 @@ function Inner() {
const t = useTheme() const t = useTheme()
const ax = useAnalytics() const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const agent = useAgent() const appviewClient = useAppviewClient()
const polling = useRef(false) const polling = useRef(false)
const unmounted = useRef(false) const unmounted = useRef(false)
const [error, setError] = useState(false) const [error, setError] = useState(false)
@@ -196,10 +196,10 @@ function Inner() {
5, 5,
() => true, () => true,
async () => { async () => {
if (!agent.session) return if (!appviewClient.did) return
if (unmounted.current) return if (unmounted.current) return
const data = await refetchAgeAssuranceServerState({agent}) const data = await refetchAgeAssuranceServerState({appviewClient})
if (data?.state.status !== 'assured') { if (data?.state.status !== 'assured') {
throw new Error( throw new Error(
@@ -214,7 +214,7 @@ function Inner() {
) )
.then(async data => { .then(async data => {
if (!data) return if (!data) return
if (!agent.session) return if (!appviewClient.did) return
if (unmounted.current) return if (unmounted.current) return
setSuccess(true) setSuccess(true)
@@ -230,7 +230,7 @@ function Inner() {
return () => { return () => {
unmounted.current = true unmounted.current = true
} }
}, [ax, agent]) }, [ax, appviewClient])
if (success) { if (success) {
return ( return (
+65 -40
View File
@@ -7,6 +7,8 @@ import {
AtpAgent, AtpAgent,
type ChatBskyActorDeclaration, type ChatBskyActorDeclaration,
} from '@atproto/api' } from '@atproto/api'
import {type Client} from '@atproto/lex'
import {getPreferences} from '@bsky.app/sdk'
import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister'
import {focusManager, QueryClient, useQuery} from '@tanstack/react-query' import {focusManager, QueryClient, useQuery} from '@tanstack/react-query'
import {persistQueryClient} from '@tanstack/react-query-persist-client' import {persistQueryClient} from '@tanstack/react-query-persist-client'
@@ -21,7 +23,7 @@ import {
snoozeBirthdateUpdateAllowedForDid, snoozeBirthdateUpdateAllowedForDid,
} from '#/state/birthdate' } from '#/state/birthdate'
import {fetchActorDeclarationRecord} from '#/state/queries/messages/actor-declaration' 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 {DEVICE_SIGNALS_SUPPORTED} from '#/ageAssurance/const'
import * as debug from '#/ageAssurance/debug' import * as debug from '#/ageAssurance/debug'
import {logger} from '#/ageAssurance/logger' import {logger} from '#/ageAssurance/logger'
@@ -37,6 +39,7 @@ import {
} from '#/ageAssurance/util' } from '#/ageAssurance/util'
import {IS_DEV} from '#/env' import {IS_DEV} from '#/env'
import {useGeolocation} from '#/geolocation' import {useGeolocation} from '#/geolocation'
import {app} from '#/lexicons'
import {device} from '#/storage' import {device} from '#/storage'
/** /**
@@ -63,12 +66,6 @@ const [, cacheHydrationPromise] = persistQueryClient({
persister, persister,
}) })
export function getDidFromAgentSession(agent: AtpAgent) {
const sessionManager = agent.sessionManager
if (!sessionManager || !sessionManager.did) return
return sessionManager.did
}
/* /*
* Optimistic data * Optimistic data
*/ */
@@ -187,7 +184,7 @@ export function useConfigQuery() {
export function createServerStateQueryKey({did}: {did: string}) { export function createServerStateQueryKey({did}: {did: string}) {
return ['serverState', did] return ['serverState', did]
} }
export async function getServerState({agent}: {agent: AtpAgent}) { export async function getServerState({appviewClient}: {appviewClient: Client}) {
if (debug.enabled && debug.serverState) if (debug.enabled && debug.serverState)
return debug.resolve(debug.serverState) return debug.resolve(debug.serverState)
const geolocation = device.get(['mergedGeolocation']) const geolocation = device.get(['mergedGeolocation'])
@@ -195,17 +192,21 @@ export async function getServerState({agent}: {agent: AtpAgent}) {
logger.error(`getServerState: missing geolocation countryCode`) logger.error(`getServerState: missing geolocation countryCode`)
return null return null
} }
const {data} = await agent.app.bsky.ageassurance.getState({ const data = await appviewClient.call(app.bsky.ageassurance.getState, {
countryCode: geolocation.countryCode, countryCode: geolocation.countryCode,
regionCode: geolocation.regionCode, regionCode: geolocation.regionCode,
}) })
const did = getDidFromAgentSession(agent) const did = appviewClient.did
if (data && did && createdAtCache.has(did)) { if (data && did && createdAtCache.has(did)) {
/* /*
* If account was just created, just use the local cache if available. On * 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 return data ?? null
} }
@@ -218,8 +219,12 @@ export function getServerStateFromCache({
createServerStateQueryKey({did}), createServerStateQueryKey({did}),
) )
} }
export async function prefetchServerState({agent}: {agent: AtpAgent}) { export async function prefetchServerState({
const did = getDidFromAgentSession(agent) appviewClient,
}: {
appviewClient: Client
}) {
const did = appviewClient.did
if (!did) return if (!did) return
@@ -234,7 +239,7 @@ export async function prefetchServerState({agent}: {agent: AtpAgent}) {
try { try {
logger.debug(`prefetchServerState: resolving...`) logger.debug(`prefetchServerState: resolving...`)
const res = await networkRetry(3, () => getServerState({agent})) const res = await networkRetry(3, () => getServerState({appviewClient}))
if (res) { if (res) {
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(qk, 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}) { export async function refetchServerState({
const did = getDidFromAgentSession(agent) appviewClient,
}: {
appviewClient: Client
}) {
const did = appviewClient.did
if (!did) return if (!did) return
logger.debug(`refetchServerState: fetching...`) logger.debug(`refetchServerState: fetching...`)
const res = await networkRetry(3, () => getServerState({agent})) const res = await networkRetry(3, () => getServerState({appviewClient}))
if (res) { if (res) {
qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>( qc.setQueryData<AppBskyAgeassuranceGetState.OutputSchema>(
createServerStateQueryKey({did}), createServerStateQueryKey({did}),
@@ -279,8 +288,8 @@ export function usePatchServerState() {
) )
} }
export function useServerStateQuery() { export function useServerStateQuery() {
const agent = useAgent() const appviewClient = useAppviewClient()
const did = getDidFromAgentSession(agent) const did = appviewClient.did
const query = useQuery( const query = useQuery(
{ {
enabled: !!did, enabled: !!did,
@@ -290,7 +299,7 @@ export function useServerStateQuery() {
}, },
queryKey: createServerStateQueryKey({did: did!}), queryKey: createServerStateQueryKey({did: did!}),
async queryFn() { async queryFn() {
return getServerState({agent}) return getServerState({appviewClient})
}, },
}, },
qc, qc,
@@ -342,15 +351,15 @@ export function createOtherRequiredDataQueryKey({did}: {did: string}) {
return ['otherRequiredData', did] return ['otherRequiredData', did]
} }
async function getOtherRequiredData({ async function getOtherRequiredData({
agent, accountClient,
}: { }: {
agent: AtpAgent accountClient: Client
}): Promise<OtherRequiredData> { }): Promise<OtherRequiredData> {
if (debug.enabled) return debug.resolve(debug.otherRequiredData) if (debug.enabled) return debug.resolve(debug.otherRequiredData)
const did = getDidFromAgentSession(agent) const did = accountClient.did
const [prefs, actorDeclaration] = await Promise.all([ const [prefs, actorDeclaration] = await Promise.all([
agent.getPreferences(), accountClient.call(getPreferences),
fetchActorDeclarationRecord({did, agent}), fetchActorDeclarationRecord({did, client: accountClient}),
]) ])
const data: OtherRequiredData = { const data: OtherRequiredData = {
birthdate: prefs.birthDate ? prefs.birthDate.toISOString() : undefined, birthdate: prefs.birthDate ? prefs.birthDate.toISOString() : undefined,
@@ -426,8 +435,12 @@ export function setOtherRequiredDataActorDeclarationCache({
next, next,
) )
} }
export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) { export async function prefetchOtherRequiredData({
const did = getDidFromAgentSession(agent) accountClient,
}: {
accountClient: Client
}) {
const did = accountClient.did
if (!did) return if (!did) return
@@ -442,7 +455,9 @@ export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) {
try { try {
logger.debug(`prefetchOtherRequiredData: resolving...`) logger.debug(`prefetchOtherRequiredData: resolving...`)
const res = await networkRetry(3, () => getOtherRequiredData({agent})) const res = await networkRetry(3, () =>
getOtherRequiredData({accountClient}),
)
qc.setQueryData<OtherRequiredData>(qk, res) qc.setQueryData<OtherRequiredData>(qk, res)
} catch (err) { } catch (err) {
const e = err as Error const e = err as Error
@@ -471,8 +486,8 @@ export function usePatchOtherRequiredData() {
) )
} }
export function useOtherRequiredDataQuery() { export function useOtherRequiredDataQuery() {
const agent = useAgent() const accountClient = usePdsClient()
const did = getDidFromAgentSession(agent) const did = accountClient.did
return useQuery( return useQuery(
{ {
enabled: !!did, enabled: !!did,
@@ -482,7 +497,7 @@ export function useOtherRequiredDataQuery() {
}, },
queryKey: createOtherRequiredDataQueryKey({did: did!}), queryKey: createOtherRequiredDataQueryKey({did: did!}),
async queryFn() { async queryFn() {
return getOtherRequiredData({agent}) return getOtherRequiredData({accountClient})
}, },
}, },
qc, qc,
@@ -577,8 +592,12 @@ export function setDeviceSignalsForRegion({
prev => ({...prev, [regionKey]: signals}), prev => ({...prev, [regionKey]: signals}),
) )
} }
export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) { export async function prefetchDeviceSignals({
const did = getDidFromAgentSession(agent) appviewClient,
}: {
appviewClient: Client
}) {
const did = appviewClient.did
if (!did) return if (!did) return
/** /**
@@ -613,8 +632,8 @@ export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) {
*/ */
} }
export function useDeviceSignalsQuery() { export function useDeviceSignalsQuery() {
const agent = useAgent() const appviewClient = useAppviewClient()
const did = getDidFromAgentSession(agent) const did = appviewClient.did
const {data: config} = useConfigQuery() const {data: config} = useConfigQuery()
const geolocation = useGeolocation() const geolocation = useGeolocation()
/* /*
@@ -659,13 +678,19 @@ export function useDeviceSignalsQuery() {
/** /**
* Helper to prefetch all age assurance data from the server. * 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([ return Promise.allSettled([
// config fetch initiated at the top of the App.platform.tsx files, awaited here // config fetch initiated at the top of the App.platform.tsx files, awaited here
configPrefetchPromise, configPrefetchPromise,
prefetchServerState({agent}), prefetchServerState({appviewClient}),
prefetchOtherRequiredData({agent}), prefetchOtherRequiredData({accountClient}),
prefetchDeviceSignals({agent}), prefetchDeviceSignals({appviewClient}),
]) ])
} }
@@ -6,7 +6,7 @@ import {Trans} from '@lingui/react/macro'
import {retry} from '#/lib/async/retry' import {retry} from '#/lib/async/retry'
import {wait} from '#/lib/async/wait' 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 {atoms as a, useTheme, web} from '#/alf'
import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge'
import {Button, ButtonText} from '#/components/Button' import {Button, ButtonText} from '#/components/Button'
@@ -84,7 +84,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
const t = useTheme() const t = useTheme()
const ax = useAnalytics() const ax = useAnalytics()
const {_} = useLingui() const {_} = useLingui()
const agent = useAgent() const appviewClient = useAppviewClient()
const polling = useRef(false) const polling = useRef(false)
const unmounted = useRef(false) const unmounted = useRef(false)
const control = useAgeAssuranceRedirectDialogControl() const control = useAgeAssuranceRedirectDialogControl()
@@ -104,10 +104,10 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
5, 5,
() => true, () => true,
async () => { async () => {
if (!agent.session) return if (!appviewClient.did) return
if (unmounted.current) return if (unmounted.current) return
const data = await refetchAgeAssuranceServerState({agent}) const data = await refetchAgeAssuranceServerState({appviewClient})
if (data?.state.status !== 'assured') { if (data?.state.status !== 'assured') {
throw new Error( throw new Error(
@@ -122,7 +122,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
) )
.then(async data => { .then(async data => {
if (!data) return if (!data) return
if (!agent.session) return if (!appviewClient.did) return
if (unmounted.current) return if (unmounted.current) return
setSuccess(true) setSuccess(true)
@@ -138,7 +138,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) {
return () => { return () => {
unmounted.current = true unmounted.current = true
} }
}, [ax, agent, control]) }, [ax, appviewClient, control])
if (success) { if (success) {
return ( return (
+40 -40
View File
@@ -2,24 +2,21 @@ import {useContext} from 'react'
import {Alert, View} from 'react-native' import {Alert, View} from 'react-native'
import {useSafeAreaInsets} from 'react-native-safe-area-context' import {useSafeAreaInsets} from 'react-native-safe-area-context'
import * as Contacts from 'expo-contacts' import * as Contacts from 'expo-contacts'
import type AtpAgent from '@atproto/api' import {type Un$Typed} from '@atproto/lex'
import {
type AppBskyActorProfile,
AppBskyContactImportContacts,
type Un$Typed,
} from '@atproto/api'
import {type Client} from '@atproto/lex' import {type Client} from '@atproto/lex'
import {toDatetimeString} from '@atproto/syntax'
import {upsertProfile} from '@bsky.app/sdk'
import {msg, t} from '@lingui/core/macro' import {msg, t} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
import {useMutation, useQueryClient} from '@tanstack/react-query' import {useMutation, useQueryClient} from '@tanstack/react-query'
import {uploadBlob} from '#/lib/api' import {uploadBlob} from '#/lib/api'
import {toLegacyBlobRef} from '#/lib/api/legacy-blob'
import {cleanError, isNetworkError} from '#/lib/strings/errors' import {cleanError, isNetworkError} from '#/lib/strings/errors'
import {matchXrpcError} from '#/lib/xrpc-error'
import {logger} from '#/logger' import {logger} from '#/logger'
import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' import {findContactsStatusQueryKey} from '#/state/queries/find-contacts'
import {useAgent, usePdsClient} from '#/state/session' import {useAppviewClient, usePdsClient} from '#/state/session'
import { import {
Context as OnboardingContext, Context as OnboardingContext,
type OnboardingAction, type OnboardingAction,
@@ -32,6 +29,7 @@ import {Loader} from '#/components/Loader'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons'
import { import {
contactsWithPhoneNumbersOnly, contactsWithPhoneNumbersOnly,
filterMatchedNumbers, filterMatchedNumbers,
@@ -56,8 +54,8 @@ export function GetContacts({
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const ax = useAnalytics() const ax = useAnalytics()
const agent = useAgent()
const pdsClient = usePdsClient() const pdsClient = usePdsClient()
const appviewClient = useAppviewClient()
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const gutters = useGutters([0, 'wide']) const gutters = useGutters([0, 'wide'])
const queryClient = useQueryClient() const queryClient = useQueryClient()
@@ -75,7 +73,7 @@ export function GetContacts({
*/ */
if (context === 'Onboarding' && maybeOnboardingContext) { if (context === 'Onboarding' && maybeOnboardingContext) {
try { try {
await createProfileRecord(agent, pdsClient, maybeOnboardingContext) await createProfileRecord(pdsClient, maybeOnboardingContext)
} catch (error) { } catch (error) {
logger.debug('Error creating profile record:', {safeMessage: error}) logger.debug('Error creating profile record:', {safeMessage: error})
} }
@@ -88,13 +86,13 @@ export function GetContacts({
) )
if (phoneNumbers.length > 0) { if (phoneNumbers.length > 0) {
const res = await agent.app.bsky.contact.importContacts({ const res = await appviewClient.call(app.bsky.contact.importContacts, {
token: state.token, token: state.token,
contacts: phoneNumbers.slice(0, MAX_UPLOAD_COUNT), contacts: phoneNumbers.slice(0, MAX_UPLOAD_COUNT),
}) })
return { return {
matches: res.data.matchesAndContactIndexes, matches: res.matchesAndContactIndexes,
indexToContactId, indexToContactId,
} }
} else { } else {
@@ -151,29 +149,30 @@ export function GetContacts({
), ),
{type: 'error'}, {type: 'error'},
) )
} else if ( return
err instanceof AppBskyContactImportContacts.TooManyContactsError }
) { switch (matchXrpcError(err, app.bsky.contact.importContacts)) {
Toast.show( case 'TooManyContacts':
_( Toast.show(
msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`, _(
), msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`,
{type: 'error'}, ),
) {type: 'error'},
} else if ( )
err instanceof AppBskyContactImportContacts.InvalidTokenError break
) { case 'InvalidToken':
Toast.show( Toast.show(
_( _(
msg`Could not upload contacts. You need to re-verify your phone number to proceed`, msg`Could not upload contacts. You need to re-verify your phone number to proceed`,
), ),
{type: 'error'}, {type: 'error'},
) )
} else { break
logger.error('Error uploading contacts', {safeMessage: err}) default:
Toast.show(_(msg`Could not upload contacts. ${cleanError(err)}`), { logger.error('Error uploading contacts', {safeMessage: err})
type: 'error', Toast.show(_(msg`Could not upload contacts. ${cleanError(err)}`), {
}) type: 'error',
})
} }
}, },
}) })
@@ -328,7 +327,6 @@ function showPermissionDeniedAlert() {
* Copied from `#/screens/Onboarding/StepFinished/index.tsx` * Copied from `#/screens/Onboarding/StepFinished/index.tsx`
*/ */
async function createProfileRecord( async function createProfileRecord(
agent: AtpAgent,
pdsClient: Client, pdsClient: Client,
onboardingContext: { onboardingContext: {
state: OnboardingState state: OnboardingState
@@ -342,19 +340,21 @@ async function createProfileRecord(
? uploadBlob(pdsClient, imageUri, imageMime) ? uploadBlob(pdsClient, imageUri, imageMime)
: undefined : undefined
await agent.upsertProfile(async existing => { await pdsClient.call(upsertProfile, async existing => {
let next: Un$Typed<AppBskyActorProfile.Record> = existing ?? {} let next: Un$Typed<app.bsky.actor.profile.Main> = existing ?? {}
if (blobPromise) { if (blobPromise) {
const res = await blobPromise const res = await blobPromise
if (res.blob) { if (res.blob) {
next.avatar = toLegacyBlobRef(res.blob) next.avatar = res.blob
} }
} }
next.displayName = '' next.displayName = ''
next.createdAt = new Date().toISOString() if (!next.createdAt) {
next.createdAt = toDatetimeString(new Date())
}
return next return next
}) })
} }
@@ -6,7 +6,6 @@ import {
type AtIdentifierString, type AtIdentifierString,
AtUri, AtUri,
type AtUriString, type AtUriString,
type DidString,
toDatetimeString, toDatetimeString,
} from '@atproto/syntax' } from '@atproto/syntax'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
@@ -86,8 +85,7 @@ export function CreateListFromStarterPackDialog({
items.map(item => { items.map(item => {
const listitemRecord: $Typed<app.bsky.graph.listitem.Main> = { const listitemRecord: $Typed<app.bsky.graph.listitem.Main> = {
$type: 'app.bsky.graph.listitem', $type: 'app.bsky.graph.listitem',
// the list view is still legacy-typed, so its strings are unbranded subject: item.subject.did,
subject: item.subject.did as DidString,
list: listUri as AtUriString, list: listUri as AtUriString,
createdAt: toDatetimeString(new Date()), createdAt: toDatetimeString(new Date()),
} }
+20 -15
View File
@@ -1,12 +1,12 @@
import {useEffect} from 'react' import {useEffect} from 'react'
import {type Agent, AppBskyActorDefs, asPredicate} from '@atproto/api' import {getPreferences, updateLiveEventPreferences} from '@bsky.app/sdk'
import {useMutation, useQueryClient} from '@tanstack/react-query' import {useMutation, useQueryClient} from '@tanstack/react-query'
import { import {
preferencesQueryKey, preferencesQueryKey,
usePreferencesQuery, usePreferencesQuery,
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {useAgent} from '#/state/session' import {usePdsClient} from '#/state/session'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import * as env from '#/env' import * as env from '#/env'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
@@ -14,10 +14,11 @@ import {
type LiveEventFeed, type LiveEventFeed,
type LiveEventFeedMetricContext, type LiveEventFeedMetricContext,
} from '#/features/liveEvents/types' } from '#/features/liveEvents/types'
import {type app} from '#/lexicons'
export type LiveEventPreferencesAction = Parameters< export type LiveEventPreferencesAction = Parameters<
Agent['updateLiveEventPreferences'] typeof updateLiveEventPreferences
>[0] & { >[1] & {
/** /**
* Flag that is internal to this hook, do not set when updating prefs * Flag that is internal to this hook, do not set when updating prefs
*/ */
@@ -38,7 +39,7 @@ export function useLiveEventPreferences() {
function useWebOnlyDebugLiveEventPreferences() { function useWebOnlyDebugLiveEventPreferences() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const pdsClient = usePdsClient()
useEffect(() => { useEffect(() => {
if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') { if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') {
@@ -46,14 +47,14 @@ function useWebOnlyDebugLiveEventPreferences() {
window.__updateLiveEventPreferences = async ( window.__updateLiveEventPreferences = async (
action: LiveEventPreferencesAction, action: LiveEventPreferencesAction,
) => { ) => {
await agent.updateLiveEventPreferences(action) await pdsClient.call(updateLiveEventPreferences, action)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
}) })
} }
} }
}, [agent, queryClient]) }, [pdsClient, queryClient])
} }
export function useUpdateLiveEventPreferences(props: { export function useUpdateLiveEventPreferences(props: {
@@ -65,10 +66,10 @@ export function useUpdateLiveEventPreferences(props: {
}) { }) {
const ax = useAnalytics() const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const pdsClient = usePdsClient()
return useMutation< return useMutation<
AppBskyActorDefs.LiveEventPreferences, app.bsky.actor.defs.LiveEventPreferences,
Error, Error,
LiveEventPreferencesAction, LiveEventPreferencesAction,
{undoAction: LiveEventPreferencesAction | null} {undoAction: LiveEventPreferencesAction | null}
@@ -108,10 +109,14 @@ export function useUpdateLiveEventPreferences(props: {
} }
}, },
mutationFn: async action => { mutationFn: async action => {
const updated = await agent.updateLiveEventPreferences(action) /*
const prefs = updated.find(p => * The SDK action returns void, so after applying the update we read the
asPredicate(AppBskyActorDefs.validateLiveEventPreferences)(p), * fresh, interpreted preferences back to obtain the updated
) * `liveEventPreferences` (the SDK extracts it from the raw prefs array for
* us, replacing the old `asPredicate(...).find(...)` lookup).
*/
await pdsClient.call(updateLiveEventPreferences, action)
const {liveEventPreferences: prefs} = await pdsClient.call(getPreferences)
switch (action.type) { switch (action.type) {
case 'hideFeed': case 'hideFeed':
@@ -138,7 +143,7 @@ export function useUpdateLiveEventPreferences(props: {
break break
} }
case 'toggleHideAllFeeds': { case 'toggleHideAllFeeds': {
if (prefs!.hideAllFeeds) { if (prefs.hideAllFeeds) {
ax.metric('liveEvents:hideAllFeedBanners', { ax.metric('liveEvents:hideAllFeedBanners', {
context: props.metricContext, context: props.metricContext,
}) })
@@ -156,7 +161,7 @@ export function useUpdateLiveEventPreferences(props: {
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
}) })
return prefs! return prefs
}, },
}) })
} }
-16
View File
@@ -1,16 +0,0 @@
import {BlobRef} from '@atproto/api'
import {type BlobRef as LexBlobRef} from '@atproto/lex'
/**
* Bridge a lex blob ref (the plain-JSON `{$type: 'blob', ref, mimeType, size}`
* that {@link uploadBlob} now returns) back to the legacy `BlobRef` class
* instance.
*
* Only needed where a blob is handed to a legacy agent write: the legacy
* lexicon blob validator checks `value instanceof BlobRef`, so a plain lex
* blob fails validation, and the legacy serializer would put the wrong shape
* on the wire. Drop each call as its write moves to the lex client.
*/
export function toLegacyBlobRef(blob: LexBlobRef): BlobRef {
return BlobRef.fromJsonRef(blob as Parameters<typeof BlobRef.fromJsonRef>[0])
}
+27 -37
View File
@@ -1,21 +1,19 @@
import {useCallback, useState} from 'react' import {useCallback, useState} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import {
type AppBskyActorDefs,
type AppBskyActorProfile,
type AppBskyGraphDefs,
AppBskyGraphStarterpack,
type Un$Typed,
} from '@atproto/api'
import {TID} from '@atproto/common-web' import {TID} from '@atproto/common-web'
import {type AtUriString} from '@atproto/syntax' import {type Un$Typed} from '@atproto/lex'
import {type AtUriString, toDatetimeString} from '@atproto/syntax'
import {
overwriteSavedFeeds,
setInterestsPref,
upsertProfile,
} from '@bsky.app/sdk'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {uploadBlob} from '#/lib/api' import {uploadBlob} from '#/lib/api'
import {toLegacyBlobRef} from '#/lib/api/legacy-blob'
import { import {
BSKY_APP_ACCOUNT_DID, BSKY_APP_ACCOUNT_DID,
DISCOVER_SAVED_FEED, DISCOVER_SAVED_FEED,
@@ -28,7 +26,7 @@ import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-p
import {getAllListMembers} from '#/state/queries/list-members' import {getAllListMembers} from '#/state/queries/list-members'
import {preferencesQueryKey} from '#/state/queries/preferences' import {preferencesQueryKey} from '#/state/queries/preferences'
import {RQKEY as profileRQKey} from '#/state/queries/profile' import {RQKEY as profileRQKey} from '#/state/queries/profile'
import {useAgent, useAppviewClient, usePdsClient} from '#/state/session' import {useAppviewClient, usePdsClient} from '#/state/session'
import {useOnboardingDispatch} from '#/state/shell' import {useOnboardingDispatch} from '#/state/shell'
import { import {
useActiveStarterPack, useActiveStarterPack,
@@ -50,6 +48,7 @@ import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/ico
import {Loader} from '#/components/Loader' import {Loader} from '#/components/Loader'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {IS_WEB} from '#/env' import {IS_WEB} from '#/env'
import {app} from '#/lexicons'
import * as bsky from '#/types/bsky' import * as bsky from '#/types/bsky'
import {ValuePropositionPager} from './ValuePropositionPager' import {ValuePropositionPager} from './ValuePropositionPager'
@@ -59,9 +58,8 @@ export function StepFinished() {
const onboardDispatch = useOnboardingDispatch() const onboardDispatch = useOnboardingDispatch()
const [saving, setSaving] = useState(false) const [saving, setSaving] = useState(false)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent()
const appviewClient = useAppviewClient()
const pdsClient = usePdsClient() const pdsClient = usePdsClient()
const appviewClient = useAppviewClient()
const requestNotificationsPermission = useRequestNotificationsPermission() const requestNotificationsPermission = useRequestNotificationsPermission()
const activeStarterPack = useActiveStarterPack() const activeStarterPack = useActiveStarterPack()
const setActiveStarterPack = useSetActiveStarterPack() const setActiveStarterPack = useSetActiveStarterPack()
@@ -71,15 +69,15 @@ export function StepFinished() {
const finishOnboarding = useCallback(async () => { const finishOnboarding = useCallback(async () => {
setSaving(true) setSaving(true)
let starterPack: AppBskyGraphDefs.StarterPackView | undefined let starterPack: app.bsky.graph.defs.StarterPackView | undefined
let listItems: AppBskyGraphDefs.ListItemView[] | undefined let listItems: app.bsky.graph.defs.ListItemView[] | undefined
if (activeStarterPack?.uri) { if (activeStarterPack?.uri) {
try { try {
const spRes = await agent.app.bsky.graph.getStarterPack({ const spRes = await appviewClient.call(app.bsky.graph.getStarterPack, {
starterPack: activeStarterPack.uri, starterPack: activeStarterPack.uri as AtUriString,
}) })
starterPack = spRes.data.starterPack starterPack = spRes.starterPack
} catch (e) { } catch (e) {
logger.error('Failed to fetch starter pack', {safeMessage: e}) logger.error('Failed to fetch starter pack', {safeMessage: e})
// don't tell the user, just get them through onboarding. // don't tell the user, just get them through onboarding.
@@ -109,19 +107,15 @@ export function StepFinished() {
appviewClient, appviewClient,
[BSKY_APP_ACCOUNT_DID, ...(listItems?.map(i => i.subject.did) ?? [])], [BSKY_APP_ACCOUNT_DID, ...(listItems?.map(i => i.subject.did) ?? [])],
starterPack starterPack
? // the starter pack view is still legacy-typed ? {uri: starterPack.uri, cid: starterPack.cid}
{
uri: starterPack.uri as AtUriString,
cid: starterPack.cid,
}
: undefined, : undefined,
), ),
(async () => { (async () => {
// Interests need to get saved first, then we can write the feeds to prefs // Interests need to get saved first, then we can write the feeds to prefs
await agent.setInterestsPref({tags: selectedInterests}) await pdsClient.call(setInterestsPref, {tags: selectedInterests})
// Default feeds that every user should have pinned when landing in the app // Default feeds that every user should have pinned when landing in the app
const feedsToSave: AppBskyActorDefs.SavedFeed[] = [ const feedsToSave: app.bsky.actor.defs.SavedFeed[] = [
{ {
...DISCOVER_SAVED_FEED, ...DISCOVER_SAVED_FEED,
id: TID.nextStr(), id: TID.nextStr(),
@@ -140,7 +134,7 @@ export function StepFinished() {
if (starterPack && starterPack.feeds?.length) { if (starterPack && starterPack.feeds?.length) {
feedsToSave.push( feedsToSave.push(
...starterPack.feeds.map(f => ({ ...starterPack.feeds.map(f => ({
type: 'feed', type: 'feed' as const,
value: f.uri, value: f.uri,
pinned: true, pinned: true,
id: TID.nextStr(), id: TID.nextStr(),
@@ -148,7 +142,7 @@ export function StepFinished() {
) )
} }
await agent.overwriteSavedFeeds(feedsToSave) await pdsClient.call(overwriteSavedFeeds, feedsToSave)
})(), })(),
(async () => { (async () => {
const {imageUri, imageMime} = profileStepResults const {imageUri, imageMime} = profileStepResults
@@ -157,13 +151,13 @@ export function StepFinished() {
? uploadBlob(pdsClient, imageUri, imageMime) ? uploadBlob(pdsClient, imageUri, imageMime)
: undefined : undefined
await agent.upsertProfile(async existing => { await pdsClient.call(upsertProfile, async existing => {
let next: Un$Typed<AppBskyActorProfile.Record> = existing ?? {} let next: Un$Typed<app.bsky.actor.profile.Main> = existing ?? {}
if (blobPromise) { if (blobPromise) {
const res = await blobPromise const res = await blobPromise
if (res.blob) { if (res.blob) {
next.avatar = toLegacyBlobRef(res.blob) next.avatar = res.blob
} }
} }
@@ -177,7 +171,7 @@ export function StepFinished() {
next.displayName = '' next.displayName = ''
if (!next.createdAt) { if (!next.createdAt) {
next.createdAt = new Date().toISOString() next.createdAt = toDatetimeString(new Date())
} }
return next return next
}) })
@@ -204,7 +198,7 @@ export function StepFinished() {
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
}), }),
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: profileRQKey(agent.session?.did ?? ''), queryKey: profileRQKey(pdsClient.did ?? ''),
}), }),
]).catch(e => { ]).catch(e => {
logger.error(e) logger.error(e)
@@ -221,10 +215,7 @@ export function StepFinished() {
usedStarterPack: Boolean(starterPack), usedStarterPack: Boolean(starterPack),
starterPackName: starterPackName:
starterPack && starterPack &&
bsky.dangerousIsType<AppBskyGraphStarterpack.Record>( bsky.isType(app.bsky.graph.starterpack, starterPack.record)
starterPack.record,
AppBskyGraphStarterpack.isRecord,
)
? starterPack.record.name ? starterPack.record.name
: undefined, : undefined,
starterPackCreator: starterPack?.creator.did, starterPackCreator: starterPack?.creator.did,
@@ -242,9 +233,8 @@ export function StepFinished() {
}, [ }, [
ax, ax,
queryClient, queryClient,
agent,
appviewClient,
pdsClient, pdsClient,
appviewClient,
dispatch, dispatch,
onboardDispatch, onboardDispatch,
activeStarterPack, activeStarterPack,
+5 -4
View File
@@ -1,5 +1,6 @@
import {useMemo, useState} from 'react' import {useMemo, useState} from 'react'
import {type TextStyle, View, type ViewStyle} from 'react-native' import {type TextStyle, View, type ViewStyle} from 'react-native'
import {setInterestsPref} from '@bsky.app/sdk'
import {msg} from '@lingui/core/macro' import {msg} from '@lingui/core/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {Trans} from '@lingui/react/macro' import {Trans} from '@lingui/react/macro'
@@ -23,7 +24,7 @@ import {createGetSuggestedUsersForDiscoverQueryKey} from '#/state/queries/trendi
import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery'
import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery'
import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery' import {createSuggestedStarterPacksQueryKey} from '#/state/queries/useSuggestedStarterPacksQuery'
import {useAgent} from '#/state/session' import {usePdsClient} from '#/state/session'
import {atoms as a, useGutters, useTheme} from '#/alf' import {atoms as a, useGutters, useTheme} from '#/alf'
import {Admonition} from '#/components/Admonition' import {Admonition} from '#/components/Admonition'
import {Divider} from '#/components/Divider' import {Divider} from '#/components/Divider'
@@ -88,7 +89,7 @@ function Inner({
setIsSaving: (isSaving: boolean) => void setIsSaving: (isSaving: boolean) => void
}) { }) {
const {_} = useLingui() const {_} = useLingui()
const agent = useAgent() const pdsClient = usePdsClient()
const qc = useQueryClient() const qc = useQueryClient()
const interestsDisplayNames = useInterestsDisplayNames() const interestsDisplayNames = useInterestsDisplayNames()
const preselectedInterests = useMemo( const preselectedInterests = useMemo(
@@ -110,7 +111,7 @@ function Inner({
setIsSaving(true) setIsSaving(true)
try { try {
await agent.setInterestsPref({tags: interests}) await pdsClient.call(setInterestsPref, {tags: interests})
qc.setQueriesData( qc.setQueriesData(
{queryKey: preferencesQueryKey}, {queryKey: preferencesQueryKey},
(old?: UsePreferencesQueryResponse) => { (old?: UsePreferencesQueryResponse) => {
@@ -157,7 +158,7 @@ function Inner({
setIsSaving(false) setIsSaving(false)
} }
}, 1500) }, 1500)
}, [_, agent, setIsSaving, qc, preselectedInterests]) }, [_, pdsClient, setIsSaving, qc, preselectedInterests])
const onChangeInterests = async (interests: string[]) => { const onChangeInterests = async (interests: string[]) => {
setInterests(interests) setInterests(interests)
+4 -3
View File
@@ -2,6 +2,7 @@ import {useState} from 'react'
import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native' import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native'
import {useReducedMotion} from 'react-native-reanimated' import {useReducedMotion} from 'react-native-reanimated'
import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' import {type AppBskyActorDefs, moderateProfile} from '@atproto/api'
import {removeNuxs} from '@bsky.app/sdk'
import {Trans, useLingui} from '@lingui/react/macro' import {Trans, useLingui} from '@lingui/react/macro'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {type NativeStackScreenProps} from '@react-navigation/native-stack'
@@ -21,7 +22,7 @@ import {clearStorage} from '#/state/persisted'
import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useModerationOpts} from '#/state/preferences/moderation-opts'
import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' import {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration'
import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile' import {useProfileQuery, useProfilesQuery} from '#/state/queries/profile'
import {useAgent} from '#/state/session' import {usePdsClient} from '#/state/session'
import {type SessionAccount, useSession, useSessionApi} from '#/state/session' import {type SessionAccount, useSession, useSessionApi} from '#/state/session'
import {useOnboardingDispatch} from '#/state/shell' import {useOnboardingDispatch} from '#/state/shell'
import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useLoggedOutViewControls} from '#/state/shell/logged-out'
@@ -385,7 +386,7 @@ function ProfilePreview({
function DevOptions() { function DevOptions() {
const {t: l} = useLingui() const {t: l} = useLingui()
const agent = useAgent() const pdsClient = usePdsClient()
const [override, setOverride] = useStorage(device, [ const [override, setOverride] = useStorage(device, [
'policyUpdateDebugOverride', 'policyUpdateDebugOverride',
]) ])
@@ -560,7 +561,7 @@ function DevOptions() {
<Button <Button
onPress={() => { onPress={() => {
device.set([PolicyUpdate202508], false) device.set([PolicyUpdate202508], false)
void agent.bskyAppRemoveNuxs([PolicyUpdate202508]) void pdsClient.call(removeNuxs, [PolicyUpdate202508])
Toast.show(`Done`, { Toast.show(`Done`, {
type: 'info', type: 'info',
}) })
+7 -4
View File
@@ -1,9 +1,10 @@
import {useMemo} from 'react' import {useMemo} from 'react'
import {setPersonalDetails} from '@bsky.app/sdk'
import {useMutation, useQueryClient} from '@tanstack/react-query' import {useMutation, useQueryClient} from '@tanstack/react-query'
import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings' import {restrictChatSettings} from '#/state/queries/messages/restrictChatSettings'
import {preferencesQueryKey} from '#/state/queries/preferences' import {preferencesQueryKey} from '#/state/queries/preferences'
import {useAgent, usePdsClient, useSession} from '#/state/session' import {usePdsClient, useSession} from '#/state/session'
import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance' import {usePatchAgeAssuranceOtherRequiredData} from '#/ageAssurance'
import {isUnderAge} from '#/ageAssurance/util' import {isUnderAge} from '#/ageAssurance/util'
import {IS_DEV} from '#/env' import {IS_DEV} from '#/env'
@@ -54,14 +55,14 @@ export function useIsBirthdateUpdateAllowed() {
export function useBirthdateMutation() { export function useBirthdateMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const {currentAccount} = useSession()
const pdsClient = usePdsClient() const pdsClient = usePdsClient()
const patchOtherRequiredData = usePatchAgeAssuranceOtherRequiredData() const patchOtherRequiredData = usePatchAgeAssuranceOtherRequiredData()
return useMutation<void, unknown, {birthDate: Date}>({ return useMutation<void, unknown, {birthDate: Date}>({
mutationFn: async ({birthDate}: {birthDate: Date}) => { mutationFn: async ({birthDate}: {birthDate: Date}) => {
const bday = birthDate.toISOString() const bday = birthDate.toISOString()
await agent.setPersonalDetails({birthDate: bday}) await pdsClient.call(setPersonalDetails, {birthDate})
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -80,7 +81,9 @@ export function useBirthdateMutation() {
* birthdate, which may change the user's age assurance access level. * birthdate, which may change the user's age assurance access level.
*/ */
void patchOtherRequiredData({birthdate: bday}) void patchOtherRequiredData({birthdate: bday})
snoozeBirthdateUpdateAllowedForDid(agent.sessionManager.did!) if (currentAccount) {
snoozeBirthdateUpdateAllowedForDid(currentAccount.did)
}
}, },
}) })
} }
+15 -6
View File
@@ -1,4 +1,6 @@
import {type AppBskyLabelerDefs} from '@atproto/api' import {type AppBskyLabelerDefs} from '@atproto/api'
import {type DidString} from '@atproto/syntax'
import {addLabeler, removeLabeler} from '@bsky.app/sdk'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {z} from 'zod' import {z} from 'zod'
@@ -9,7 +11,7 @@ import {
usePreferencesQuery, usePreferencesQuery,
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {createQueryKey} from '#/state/queries/util' import {createQueryKey} from '#/state/queries/util'
import {useAgent} from '#/state/session' import {useAgent, usePdsClient} from '#/state/session'
const labelerInfoQueryKeyRoot = 'labeler-info' const labelerInfoQueryKeyRoot = 'labeler-info'
export const labelerInfoQueryKey = (did: string) => [ export const labelerInfoQueryKey = (did: string) => [
@@ -78,11 +80,13 @@ export function useLabelersDetailedInfoQuery({dids}: {dids: string[]}) {
export function useRemoveLabelersMutation() { export function useRemoveLabelersMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
async mutationFn({dids}: {dids: string[]}) { async mutationFn({dids}: {dids: string[]}) {
await Promise.all(dids.map(did => agent.removeLabeler(did))) await Promise.all(
dids.map(did => client.call(removeLabeler, did as DidString)),
)
}, },
async onSuccess() { async onSuccess() {
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
@@ -95,6 +99,7 @@ export function useRemoveLabelersMutation() {
export function useLabelerSubscriptionMutation() { export function useLabelerSubscriptionMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const agent = useAgent()
const pdsClient = usePdsClient()
const preferences = usePreferencesQuery() const preferences = usePreferencesQuery()
return useMutation({ return useMutation({
@@ -136,7 +141,11 @@ export function useLabelerSubscriptionMutation() {
} }
} }
if (invalidLabelers.length) { if (invalidLabelers.length) {
await Promise.all(invalidLabelers.map(did => agent.removeLabeler(did))) await Promise.all(
invalidLabelers.map(did =>
pdsClient.call(removeLabeler, did as DidString),
),
)
} }
if (subscribe) { if (subscribe) {
@@ -144,9 +153,9 @@ export function useLabelerSubscriptionMutation() {
if (labelerCount >= MAX_LABELERS) { if (labelerCount >= MAX_LABELERS) {
throw new Error('MAX_LABELERS') throw new Error('MAX_LABELERS')
} }
await agent.addLabeler(did) await pdsClient.call(addLabeler, did as DidString)
} else { } else {
await agent.removeLabeler(did) await pdsClient.call(removeLabeler, did as DidString)
} }
}, },
async onSuccess() { async onSuccess() {
+10 -5
View File
@@ -1,22 +1,27 @@
import {type AtUriString} from '@atproto/syntax'
import {deleteLike, like} from '@bsky.app/sdk'
import {useMutation} from '@tanstack/react-query' import {useMutation} from '@tanstack/react-query'
import {useAgent} from '#/state/session' import {usePdsClient} from '#/state/session'
export function useLikeMutation() { export function useLikeMutation() {
const agent = useAgent() const pdsClient = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async ({uri, cid}: {uri: string; cid: string}) => { mutationFn: async ({uri, cid}: {uri: string; cid: string}) => {
const res = await agent.like(uri, cid) const res = await pdsClient.call(like, {
uri: uri as AtUriString,
cid,
})
return {uri: res.uri} return {uri: res.uri}
}, },
}) })
} }
export function useUnlikeMutation() { export function useUnlikeMutation() {
const agent = useAgent() const pdsClient = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async ({uri}: {uri: string}) => { mutationFn: async ({uri}: {uri: string}) => {
await agent.deleteLike(uri) await pdsClient.call(deleteLike, uri as AtUriString)
}, },
}) })
} }
+1 -1
View File
@@ -61,7 +61,7 @@ export function useAllListMembersQuery(uri?: string) {
export async function getAllListMembers(client: Client, uri: string) { export async function getAllListMembers(client: Client, uri: string) {
let hasMore = true let hasMore = true
let cursor: string | undefined let cursor: string | undefined
const listItems: AppBskyGraphDefs.ListItemView[] = [] const listItems: app.bsky.graph.defs.ListItemView[] = []
// We want to cap this at 6 pages, just for anything weird happening with the api // We want to cap this at 6 pages, just for anything weird happening with the api
let i = 0 let i = 0
while (hasMore && i < 6) { while (hasMore && i < 6) {
+12 -14
View File
@@ -6,6 +6,12 @@ import {
type AtUriString, type AtUriString,
toDatetimeString, toDatetimeString,
} from '@atproto/syntax' } from '@atproto/syntax'
import {
blockActorList,
muteActorList,
unblockActorList,
unmuteActorList,
} from '@bsky.app/sdk'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import chunk from 'lodash.chunk' import chunk from 'lodash.chunk'
@@ -13,12 +19,7 @@ import {uploadBlob} from '#/lib/api'
import {until} from '#/lib/async/until' import {until} from '#/lib/async/until'
import {type ImageMeta} from '#/state/gallery' import {type ImageMeta} from '#/state/gallery'
import {STALE} from '#/state/queries' import {STALE} from '#/state/queries'
import { import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
useAgent,
useAppviewClient,
usePdsClient,
useSession,
} from '#/state/session'
import {app, com} from '#/lexicons' import {app, com} from '#/lexicons'
import {FEED_INFO_RQKEY_ROOT} from './feed' import {FEED_INFO_RQKEY_ROOT} from './feed'
import {invalidate as invalidateMyLists} from './my-lists' import {invalidate as invalidateMyLists} from './my-lists'
@@ -255,15 +256,13 @@ export function useListDeleteMutation() {
export function useListMuteMutation() { export function useListMuteMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent()
const appviewClient = useAppviewClient() const appviewClient = useAppviewClient()
return useMutation<void, Error, {uri: string; mute: boolean}>({ return useMutation<void, Error, {uri: string; mute: boolean}>({
mutationFn: async ({uri, mute}) => { mutationFn: async ({uri, mute}) => {
// `muteModList`/`unmuteModList` are preference writes, migrated in wave B
if (mute) { if (mute) {
await agent.muteModList(uri) await appviewClient.call(muteActorList, {list: uri as AtUriString})
} else { } else {
await agent.unmuteModList(uri) await appviewClient.call(unmuteActorList, {list: uri as AtUriString})
} }
await whenAppViewReady(appviewClient, uri, v => { await whenAppViewReady(appviewClient, uri, v => {
@@ -280,15 +279,14 @@ export function useListMuteMutation() {
export function useListBlockMutation() { export function useListBlockMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent()
const appviewClient = useAppviewClient() const appviewClient = useAppviewClient()
const pdsClient = usePdsClient()
return useMutation<void, Error, {uri: string; block: boolean}>({ return useMutation<void, Error, {uri: string; block: boolean}>({
mutationFn: async ({uri, block}) => { mutationFn: async ({uri, block}) => {
// `blockModList`/`unblockModList` write a block record, migrated in wave B
if (block) { if (block) {
await agent.blockModList(uri) await pdsClient.call(blockActorList, {list: uri as AtUriString})
} else { } else {
await agent.unblockModList(uri) await pdsClient.call(unblockActorList, {list: uri as AtUriString})
} }
await whenAppViewReady(appviewClient, uri, v => { await whenAppViewReady(appviewClient, uri, v => {
@@ -1,15 +1,12 @@
import type AtpAgent from '@atproto/api' import {type AppBskyActorDefs} from '@atproto/api'
import { import {type Client} from '@atproto/lex'
type AppBskyActorDefs,
type ChatBskyActorDeclaration,
} from '@atproto/api'
import {type DidString} from '@atproto/syntax' import {type DidString} from '@atproto/syntax'
import {useMutation, useQueryClient} from '@tanstack/react-query' import {useMutation, useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger' import {logger} from '#/logger'
import {usePdsClient, useSession} from '#/state/session' import {usePdsClient, useSession} from '#/state/session'
import {resolveAllowGroupInvites} from '#/components/dms/util' import {resolveAllowGroupInvites} from '#/components/dms/util'
import {com} from '#/lexicons' import {chat, com} from '#/lexicons'
import {RQKEY as PROFILE_RKEY} from '../profile' import {RQKEY as PROFILE_RKEY} from '../profile'
export function useUpdateActorDeclaration({ 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({ export async function fetchActorDeclarationRecord({
agent, client,
did, did,
}: { }: {
agent: AtpAgent client: Client
did?: string did?: string
}) { }) {
if (!did) return if (!did) return
const res = await agent.com.atproto.repo const res = await client
.getRecord({ .get(chat.bsky.actor.declaration, {repo: did as DidString, rkey: 'self'})
repo: did,
collection: 'chat.bsky.actor.declaration',
rkey: 'self',
})
.catch(_e => undefined) .catch(_e => undefined)
return res?.data.value as ChatBskyActorDeclaration.Main return res?.value
} }
+16 -7
View File
@@ -1,3 +1,4 @@
import {removeNuxs, upsertNux} from '@bsky.app/sdk'
import {useMutation, useQueryClient} from '@tanstack/react-query' import {useMutation, useQueryClient} from '@tanstack/react-query'
import {type AppNux, type Nux} from '#/state/queries/nuxs/definitions' import {type AppNux, type Nux} from '#/state/queries/nuxs/definitions'
@@ -6,7 +7,8 @@ import {
preferencesQueryKey, preferencesQueryKey,
usePreferencesQuery, usePreferencesQuery,
} from '#/state/queries/preferences' } from '#/state/queries/preferences'
import {useAgent} from '#/state/session' import {usePdsClient} from '#/state/session'
import {type app} from '#/lexicons'
export {Nux} from '#/state/queries/nuxs/definitions' export {Nux} from '#/state/queries/nuxs/definitions'
@@ -42,11 +44,11 @@ export function useNuxs():
// if (__DEV__) { // if (__DEV__) {
// const queryClient = useQueryClient() // const queryClient = useQueryClient()
// const agent = useAgent() // const pdsClient = usePdsClient()
// // @ts-ignore // // @ts-ignore
// window.clearNux = async (ids: string[]) => { // window.clearNux = async (ids: string[]) => {
// await agent.bskyAppRemoveNuxs(ids) // await pdsClient.call(removeNuxs, ids)
// // triggers a refetch // // triggers a refetch
// await queryClient.invalidateQueries({ // await queryClient.invalidateQueries({
// queryKey: preferencesQueryKey, // queryKey: preferencesQueryKey,
@@ -97,12 +99,19 @@ export function useNux<T extends Nux>(
export function useSaveNux() { export function useSaveNux() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const pdsClient = usePdsClient()
return useMutation({ return useMutation({
retry: 3, retry: 3,
mutationFn: async (nux: AppNux) => { mutationFn: async (nux: AppNux) => {
await agent.bskyAppUpsertNux(serializeAppNux(nux)) /*
* `serializeAppNux` still returns the legacy `Nux`, whose strings are
* unbranded; it is validated against the same schema the action expects.
*/
await pdsClient.call(
upsertNux,
serializeAppNux(nux) as app.bsky.actor.defs.Nux,
)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -113,12 +122,12 @@ export function useSaveNux() {
export function useResetNuxs() { export function useResetNuxs() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const pdsClient = usePdsClient()
return useMutation({ return useMutation({
retry: 3, retry: 3,
mutationFn: async (ids: string[]) => { mutationFn: async (ids: string[]) => {
await agent.bskyAppRemoveNuxs(ids) await pdsClient.call(removeNuxs, ids)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
+6 -2
View File
@@ -6,7 +6,7 @@ import {useMutation, useQueryClient} from '@tanstack/react-query'
import {logger} from '#/logger' import {logger} from '#/logger'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import * as Toast from '#/components/Toast' import * as Toast from '#/components/Toast'
import {app} from '#/lexicons' import {app, type com} from '#/lexicons'
import {updatePostShadow} from '../cache/post-shadow' import {updatePostShadow} from '../cache/post-shadow'
import {useAppviewClient, useSession} from '../session' import {useAppviewClient, useSession} from '../session'
import {useProfileUpdateMutation} from './profile' import {useProfileUpdateMutation} from './profile'
@@ -47,7 +47,11 @@ export function usePinnedPostMutation() {
profile, profile,
updates: existing => { updates: existing => {
existing.pinnedPost = pinCurrentPost existing.pinnedPost = pinCurrentPost
? {uri: postUri, cid: postCid} ? // the caller's uri/cid are unbranded strings
({
uri: postUri,
cid: postCid,
} as com.atproto.repo.strongRef.Main)
: undefined : undefined
return existing return existing
}, },
@@ -1,8 +1,9 @@
import {type AppBskyActorDefs} from '@atproto/api' import {setPostInteractionSettings} from '@bsky.app/sdk'
import {useMutation, useQueryClient} from '@tanstack/react-query' import {useMutation, useQueryClient} from '@tanstack/react-query'
import {preferencesQueryKey} from '#/state/queries/preferences' import {preferencesQueryKey} from '#/state/queries/preferences'
import {useAgent} from '#/state/session' import {usePdsClient} from '#/state/session'
import {type app} from '#/lexicons'
export function usePostInteractionSettingsMutation({ export function usePostInteractionSettingsMutation({
onError, onError,
@@ -12,10 +13,10 @@ export function usePostInteractionSettingsMutation({
onSettled?: () => void onSettled?: () => void
} = {}) { } = {}) {
const qc = useQueryClient() const qc = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
async mutationFn(props: AppBskyActorDefs.PostInteractionSettingsPref) { async mutationFn(props: app.bsky.actor.defs.PostInteractionSettingsPref) {
await agent.setPostInteractionSettings(props) await client.call(setPostInteractionSettings, props)
}, },
async onSuccess() { async onSuccess() {
await qc.invalidateQueries({ await qc.invalidateQueries({
+39 -17
View File
@@ -1,5 +1,7 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {type AppBskyActorDefs, type AppBskyFeedDefs, AtUri} from '@atproto/api' import {type AppBskyActorDefs, type AppBskyFeedDefs, AtUri} from '@atproto/api'
import {type AtUriString} from '@atproto/syntax'
import {deleteLike, deletePost, deleteRepost, like, repost} from '@bsky.app/sdk'
import { import {
type QueryClient, type QueryClient,
useMutation, useMutation,
@@ -10,10 +12,16 @@ import {
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
import {updatePostShadow} from '#/state/cache/post-shadow' import {updatePostShadow} from '#/state/cache/post-shadow'
import {type Shadow} from '#/state/cache/types' import {type Shadow} from '#/state/cache/types'
import {useAgent, useSession} from '#/state/session' import {
useAgent,
useAppviewClient,
usePdsClient,
useSession,
} from '#/state/session'
import * as userActionHistory from '#/state/userActionHistory' import * as userActionHistory from '#/state/userActionHistory'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type Metrics, toClout} from '#/analytics/metrics' import {type Metrics, toClout} from '#/analytics/metrics'
import {app} from '#/lexicons'
import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes' import {useIsThreadMuted, useSetThreadMute} from '../cache/thread-mutes'
import {findProfileQueryData} from './profile' import {findProfileQueryData} from './profile'
@@ -184,7 +192,7 @@ function usePostLikeMutation(
const {currentAccount} = useSession() const {currentAccount} = useSession()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const postAuthor = post.author const postAuthor = post.author
const agent = useAgent() const pdsClient = usePdsClient()
const ax = useAnalytics() const ax = useAnalytics()
return useMutation< return useMutation<
{uri: string}, // responds with the uri of the like {uri: string}, // responds with the uri of the like
@@ -215,7 +223,11 @@ function usePostLikeMutation(
: undefined, : undefined,
feedDescriptor: feedDescriptor, feedDescriptor: feedDescriptor,
}) })
return agent.like(uri, cid, via) return pdsClient.call(like, {
uri: uri as AtUriString,
cid: cid,
via: via ? {uri: via.uri as AtUriString, cid: via.cid} : undefined,
})
}, },
}) })
} }
@@ -225,7 +237,7 @@ function usePostUnlikeMutation(
logContext: Metrics['post:unlike']['logContext'], logContext: Metrics['post:unlike']['logContext'],
post: Shadow<AppBskyFeedDefs.PostView>, post: Shadow<AppBskyFeedDefs.PostView>,
) { ) {
const agent = useAgent() const pdsClient = usePdsClient()
const ax = useAnalytics() const ax = useAnalytics()
return useMutation<void, Error, {postUri: string; likeUri: string}>({ return useMutation<void, Error, {postUri: string; likeUri: string}>({
mutationFn: ({postUri, likeUri}) => { mutationFn: ({postUri, likeUri}) => {
@@ -235,7 +247,7 @@ function usePostUnlikeMutation(
logContext, logContext,
feedDescriptor, feedDescriptor,
}) })
return agent.deleteLike(likeUri) return pdsClient.call(deleteLike, likeUri as AtUriString)
}, },
}) })
} }
@@ -309,7 +321,7 @@ function usePostRepostMutation(
logContext: Metrics['post:repost']['logContext'], logContext: Metrics['post:repost']['logContext'],
post: Shadow<AppBskyFeedDefs.PostView>, post: Shadow<AppBskyFeedDefs.PostView>,
) { ) {
const agent = useAgent() const pdsClient = usePdsClient()
const ax = useAnalytics() const ax = useAnalytics()
return useMutation< return useMutation<
{uri: string}, // responds with the uri of the repost {uri: string}, // responds with the uri of the repost
@@ -323,7 +335,11 @@ function usePostRepostMutation(
logContext, logContext,
feedDescriptor, feedDescriptor,
}) })
return agent.repost(uri, cid, via) return pdsClient.call(repost, {
uri: uri as AtUriString,
cid: cid,
via: via ? {uri: via.uri as AtUriString, cid: via.cid} : undefined,
})
}, },
}) })
} }
@@ -333,7 +349,7 @@ function usePostUnrepostMutation(
logContext: Metrics['post:unrepost']['logContext'], logContext: Metrics['post:unrepost']['logContext'],
post: Shadow<AppBskyFeedDefs.PostView>, post: Shadow<AppBskyFeedDefs.PostView>,
) { ) {
const agent = useAgent() const pdsClient = usePdsClient()
const ax = useAnalytics() const ax = useAnalytics()
return useMutation<void, Error, {postUri: string; repostUri: string}>({ return useMutation<void, Error, {postUri: string; repostUri: string}>({
mutationFn: ({postUri, repostUri}) => { mutationFn: ({postUri, repostUri}) => {
@@ -343,17 +359,17 @@ function usePostUnrepostMutation(
logContext, logContext,
feedDescriptor, feedDescriptor,
}) })
return agent.deleteRepost(repostUri) return pdsClient.call(deleteRepost, repostUri as AtUriString)
}, },
}) })
} }
export function usePostDeleteMutation() { export function usePostDeleteMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const pdsClient = usePdsClient()
return useMutation<void, Error, {uri: string}>({ return useMutation<void, Error, {uri: string}>({
mutationFn: async ({uri}) => { mutationFn: async ({uri}) => {
await agent.deletePost(uri) await pdsClient.call(deletePost, uri as AtUriString)
}, },
onSuccess(_, variables) { onSuccess(_, variables) {
updatePostShadow(queryClient, variables.uri, {isDeleted: true}) updatePostShadow(queryClient, variables.uri, {isDeleted: true})
@@ -407,23 +423,29 @@ export function useThreadMuteMutationQueue(
} }
function useThreadMuteMutation() { function useThreadMuteMutation() {
const agent = useAgent() const appviewClient = useAppviewClient()
return useMutation< return useMutation<
{}, {},
Error, Error,
{uri: string} // the root post's uri {uri: string} // the root post's uri
>({ >({
mutationFn: ({uri}) => { mutationFn: async ({uri}) => {
return agent.api.app.bsky.graph.muteThread({root: uri}) await appviewClient.call(app.bsky.graph.muteThread, {
root: uri as AtUriString,
})
return {}
}, },
}) })
} }
function useThreadUnmuteMutation() { function useThreadUnmuteMutation() {
const agent = useAgent() const appviewClient = useAppviewClient()
return useMutation<{}, Error, {uri: string}>({ return useMutation<{}, Error, {uri: string}>({
mutationFn: ({uri}) => { mutationFn: async ({uri}) => {
return agent.api.app.bsky.graph.unmuteThread({root: uri}) await appviewClient.call(app.bsky.graph.unmuteThread, {
root: uri as AtUriString,
})
return {}
}, },
}) })
} }
+114 -54
View File
@@ -1,9 +1,28 @@
import {useCallback} from 'react' import {useCallback} from 'react'
import {type AppBskyActorDefs} from '@atproto/api'
import {type DidString} from '@atproto/syntax'
import { import {
type AppBskyActorDefs, addSavedFeeds,
type BskyFeedViewPreference, type BskyFeedViewPreference,
type LabelPreference, dismissNudges,
} from '@atproto/api' getPreferences,
overwriteSavedFeeds,
queueNudges,
removeMutedWord,
removeMutedWords,
removeSavedFeeds,
setActiveProgressGuide,
setAdultContentEnabled,
setContentLabelPref,
setFeedViewPrefs,
setIsBetaUser,
setThreadViewPrefs,
setVerificationPrefs,
updateMutedWord,
updateSavedFeeds,
upsertMutedWords,
} from '@bsky.app/sdk'
import {type LabelPreference} from '@bsky.app/sdk/moderation'
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query'
import {PROD_DEFAULT_FEED} from '#/lib/constants' import {PROD_DEFAULT_FEED} from '#/lib/constants'
@@ -20,11 +39,12 @@ import {
type UsePreferencesQueryResponse, type UsePreferencesQueryResponse,
} from '#/state/queries/preferences/types' } from '#/state/queries/preferences/types'
import {createQueryKey} from '#/state/queries/util' import {createQueryKey} from '#/state/queries/util'
import {useAgent} from '#/state/session' import {useAgent, usePdsClient} from '#/state/session'
import {saveLabelers} from '#/state/session/moderation' import {saveLabelers} from '#/state/session/moderation'
import {useAgeAssurance} from '#/ageAssurance' import {useAgeAssurance} from '#/ageAssurance'
import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util' import {makeAgeRestrictedModerationPrefs} from '#/ageAssurance/util'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {app} from '#/lexicons'
export * from '#/state/queries/preferences/const' export * from '#/state/queries/preferences/const'
export * from '#/state/queries/preferences/moderation' export * from '#/state/queries/preferences/moderation'
@@ -37,6 +57,7 @@ export const preferencesQueryKey = createQueryKey(
) )
export function usePreferencesQuery() { export function usePreferencesQuery() {
const client = usePdsClient()
const agent = useAgent() const agent = useAgent()
const aa = useAgeAssurance() const aa = useAgeAssurance()
@@ -47,18 +68,34 @@ export function usePreferencesQuery() {
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
gcTime: GCTIME.INFINITY, gcTime: GCTIME.INFINITY,
queryFn: async () => { queryFn: async () => {
if (!agent.did) { if (!client.did) {
return DEFAULT_LOGGED_OUT_PREFERENCES return DEFAULT_LOGGED_OUT_PREFERENCES
} else { } else {
const res = await agent.getPreferences() const res = await client.call(getPreferences)
const labelerDids = res.moderationPrefs.labelers.map(l => l.did)
// save to local storage to ensure there are labels on initial requests // save to local storage to ensure there are labels on initial requests
saveLabelers( saveLabelers(client.did, labelerDids)
agent.did,
res.moderationPrefs.labelers.map(l => l.did),
)
const preferences: UsePreferencesQueryResponse = { /*
* `BskyAgent.getPreferences` used to call `configureLabelers` itself,
* so subscribing to a labeler took effect on the very next appview
* read. The sdk action has no such side effect, and appview reads still
* inherit `atproto-accept-labelers` from the agent's fetch handler, so
* apply the subscriptions there rather than on the wrapping client -
* setting them on both would emit the header twice.
*/
agent.configureLabelers(labelerDids)
/*
* The sdk's `BskyPreferences` is field-for-field identical to the
* legacy one that `UsePreferencesQueryResponse` is still derived from;
* only its strings are branded (`AtUriString`, `DidString`). Cast at
* this one seam so every downstream consumer of the response - notably
* the `moderationPrefs` readers - keeps its current types.
*/
const preferences = {
...res, ...res,
savedFeeds: res.savedFeeds.filter(f => f.type !== 'unknown'), savedFeeds: res.savedFeeds.filter(f => f.type !== 'unknown'),
/** /**
@@ -74,7 +111,7 @@ export function usePreferencesQuery() {
...(res.threadViewPrefs ?? {}), ...(res.threadViewPrefs ?? {}),
}, },
userAge: res.birthDate ? getAge(res.birthDate) : undefined, userAge: res.birthDate ? getAge(res.birthDate) : undefined,
} } as UsePreferencesQueryResponse
return preferences return preferences
} }
}, },
@@ -113,11 +150,11 @@ export function usePreferencesQuery() {
export function useClearPreferencesMutation() { export function useClearPreferencesMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async () => { mutationFn: async () => {
await agent.app.bsky.actor.putPreferences({preferences: []}) await client.call(app.bsky.actor.putPreferences, {preferences: []})
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -128,7 +165,7 @@ export function useClearPreferencesMutation() {
export function usePreferencesSetContentLabelMutation() { export function usePreferencesSetContentLabelMutation() {
const ax = useAnalytics() const ax = useAnalytics()
const agent = useAgent() const client = usePdsClient()
const queryClient = useQueryClient() const queryClient = useQueryClient()
return useMutation< return useMutation<
@@ -137,7 +174,11 @@ export function usePreferencesSetContentLabelMutation() {
{label: string; visibility: LabelPreference; labelerDid: string | undefined} {label: string; visibility: LabelPreference; labelerDid: string | undefined}
>({ >({
mutationFn: async ({label, visibility, labelerDid}) => { mutationFn: async ({label, visibility, labelerDid}) => {
await agent.setContentLabelPref(label, visibility, labelerDid) await client.call(setContentLabelPref, {
key: label,
value: visibility,
labelerDid: labelerDid as DidString | undefined,
})
ax.metric('moderation:changeLabelPreference', {preference: visibility}) ax.metric('moderation:changeLabelPreference', {preference: visibility})
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
@@ -149,7 +190,7 @@ export function usePreferencesSetContentLabelMutation() {
export function useSetContentLabelMutation() { export function useSetContentLabelMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({
@@ -161,7 +202,11 @@ export function useSetContentLabelMutation() {
visibility: LabelPreference visibility: LabelPreference
labelerDid?: string labelerDid?: string
}) => { }) => {
await agent.setContentLabelPref(label, visibility, labelerDid) await client.call(setContentLabelPref, {
key: label,
value: visibility,
labelerDid: labelerDid as DidString | undefined,
})
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -172,11 +217,11 @@ export function useSetContentLabelMutation() {
export function usePreferencesSetAdultContentMutation() { export function usePreferencesSetAdultContentMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation<void, unknown, {enabled: boolean}>({ return useMutation<void, unknown, {enabled: boolean}>({
mutationFn: async ({enabled}) => { mutationFn: async ({enabled}) => {
await agent.setAdultContentEnabled(enabled) await client.call(setAdultContentEnabled, enabled)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -187,7 +232,7 @@ export function usePreferencesSetAdultContentMutation() {
export function useSetFeedViewPreferencesMutation() { export function useSetFeedViewPreferencesMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation<void, unknown, Partial<BskyFeedViewPreference>>({ return useMutation<void, unknown, Partial<BskyFeedViewPreference>>({
mutationFn: async prefs => { mutationFn: async prefs => {
@@ -195,7 +240,7 @@ export function useSetFeedViewPreferencesMutation() {
* special handling here, merged into `feedViewPrefs` above, since * special handling here, merged into `feedViewPrefs` above, since
* following was previously called `home` * following was previously called `home`
*/ */
await agent.setFeedViewPrefs('home', prefs) await client.call(setFeedViewPrefs, {feed: 'home', ...prefs})
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -212,11 +257,11 @@ export function useSetThreadViewPreferencesMutation({
onError?: (error: unknown) => void onError?: (error: unknown) => void
}) { }) {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation<void, unknown, Partial<ThreadViewPreferences>>({ return useMutation<void, unknown, Partial<ThreadViewPreferences>>({
mutationFn: async prefs => { mutationFn: async prefs => {
await agent.setThreadViewPrefs(prefs) await client.call(setThreadViewPrefs, prefs)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -229,11 +274,11 @@ export function useSetThreadViewPreferencesMutation({
export function useOverwriteSavedFeedsMutation() { export function useOverwriteSavedFeedsMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation<void, unknown, AppBskyActorDefs.SavedFeed[]>({ return useMutation<void, unknown, AppBskyActorDefs.SavedFeed[]>({
mutationFn: async savedFeeds => { mutationFn: async savedFeeds => {
await agent.overwriteSavedFeeds(savedFeeds) await client.call(overwriteSavedFeeds, savedFeeds)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -244,7 +289,7 @@ export function useOverwriteSavedFeedsMutation() {
export function useAddSavedFeedsMutation() { export function useAddSavedFeedsMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation< return useMutation<
void, void,
@@ -252,7 +297,7 @@ export function useAddSavedFeedsMutation() {
Pick<AppBskyActorDefs.SavedFeed, 'type' | 'value' | 'pinned'>[] Pick<AppBskyActorDefs.SavedFeed, 'type' | 'value' | 'pinned'>[]
>({ >({
mutationFn: async savedFeeds => { mutationFn: async savedFeeds => {
await agent.addSavedFeeds(savedFeeds) await client.call(addSavedFeeds, savedFeeds)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -263,11 +308,11 @@ export function useAddSavedFeedsMutation() {
export function useRemoveFeedMutation() { export function useRemoveFeedMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation<void, unknown, Pick<AppBskyActorDefs.SavedFeed, 'id'>>({ return useMutation<void, unknown, Pick<AppBskyActorDefs.SavedFeed, 'id'>>({
mutationFn: async savedFeed => { mutationFn: async savedFeed => {
await agent.removeSavedFeeds([savedFeed.id]) await client.call(removeSavedFeeds, [savedFeed.id])
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -278,7 +323,7 @@ export function useRemoveFeedMutation() {
export function useReplaceForYouWithDiscoverFeedMutation() { export function useReplaceForYouWithDiscoverFeedMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({
@@ -289,10 +334,10 @@ export function useReplaceForYouWithDiscoverFeedMutation() {
discoverFeedConfig: AppBskyActorDefs.SavedFeed | undefined discoverFeedConfig: AppBskyActorDefs.SavedFeed | undefined
}) => { }) => {
if (forYouFeedConfig) { if (forYouFeedConfig) {
await agent.removeSavedFeeds([forYouFeedConfig.id]) await client.call(removeSavedFeeds, [forYouFeedConfig.id])
} }
if (!discoverFeedConfig) { if (!discoverFeedConfig) {
await agent.addSavedFeeds([ await client.call(addSavedFeeds, [
{ {
type: 'feed', type: 'feed',
value: PROD_DEFAULT_FEED('whats-hot'), value: PROD_DEFAULT_FEED('whats-hot'),
@@ -300,7 +345,7 @@ export function useReplaceForYouWithDiscoverFeedMutation() {
}, },
]) ])
} else { } else {
await agent.updateSavedFeeds([ await client.call(updateSavedFeeds, [
{ {
...discoverFeedConfig, ...discoverFeedConfig,
pinned: true, pinned: true,
@@ -317,11 +362,11 @@ export function useReplaceForYouWithDiscoverFeedMutation() {
export function useUpdateSavedFeedsMutation() { export function useUpdateSavedFeedsMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation<void, unknown, AppBskyActorDefs.SavedFeed[]>({ return useMutation<void, unknown, AppBskyActorDefs.SavedFeed[]>({
mutationFn: async feeds => { mutationFn: async feeds => {
await agent.updateSavedFeeds(feeds) await client.call(updateSavedFeeds, feeds)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
@@ -333,11 +378,14 @@ export function useUpdateSavedFeedsMutation() {
export function useUpsertMutedWordsMutation() { export function useUpsertMutedWordsMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => { mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => {
await agent.upsertMutedWords(mutedWords) await client.call(
upsertMutedWords,
mutedWords as app.bsky.actor.defs.MutedWord[],
)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -348,11 +396,14 @@ export function useUpsertMutedWordsMutation() {
export function useUpdateMutedWordMutation() { export function useUpdateMutedWordMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => { mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => {
await agent.updateMutedWord(mutedWord) await client.call(
updateMutedWord,
mutedWord as app.bsky.actor.defs.MutedWord,
)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -363,11 +414,14 @@ export function useUpdateMutedWordMutation() {
export function useRemoveMutedWordMutation() { export function useRemoveMutedWordMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => { mutationFn: async (mutedWord: AppBskyActorDefs.MutedWord) => {
await agent.removeMutedWord(mutedWord) await client.call(
removeMutedWord,
mutedWord as app.bsky.actor.defs.MutedWord,
)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -378,11 +432,14 @@ export function useRemoveMutedWordMutation() {
export function useRemoveMutedWordsMutation() { export function useRemoveMutedWordsMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => { mutationFn: async (mutedWords: AppBskyActorDefs.MutedWord[]) => {
await agent.removeMutedWords(mutedWords) await client.call(
removeMutedWords,
mutedWords as app.bsky.actor.defs.MutedWord[],
)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -393,11 +450,11 @@ export function useRemoveMutedWordsMutation() {
export function useQueueNudgesMutation() { export function useQueueNudgesMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async (nudges: string | string[]) => { mutationFn: async (nudges: string | string[]) => {
await agent.bskyAppQueueNudges(nudges) await client.call(queueNudges, Array.isArray(nudges) ? nudges : [nudges])
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -408,11 +465,14 @@ export function useQueueNudgesMutation() {
export function useDismissNudgesMutation() { export function useDismissNudgesMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async (nudges: string | string[]) => { mutationFn: async (nudges: string | string[]) => {
await agent.bskyAppDismissNudges(nudges) await client.call(
dismissNudges,
Array.isArray(nudges) ? nudges : [nudges],
)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -423,13 +483,13 @@ export function useDismissNudgesMutation() {
export function useSetActiveProgressGuideMutation() { export function useSetActiveProgressGuideMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async ( mutationFn: async (
guide: AppBskyActorDefs.BskyAppProgressGuide | undefined, guide: AppBskyActorDefs.BskyAppProgressGuide | undefined,
) => { ) => {
await agent.bskyAppSetActiveProgressGuide(guide) await client.call(setActiveProgressGuide, guide)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -440,11 +500,11 @@ export function useSetActiveProgressGuideMutation() {
export function useSetIsBetaUserMutation() { export function useSetIsBetaUserMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation({ return useMutation({
mutationFn: async (isBetaUser: boolean) => { mutationFn: async (isBetaUser: boolean) => {
await agent.setIsBetaUser(isBetaUser) await client.call(setIsBetaUser, isBetaUser)
// triggers a refetch // triggers a refetch
await queryClient.invalidateQueries({ await queryClient.invalidateQueries({
queryKey: preferencesQueryKey, queryKey: preferencesQueryKey,
@@ -456,11 +516,11 @@ export function useSetIsBetaUserMutation() {
export function useSetVerificationPrefsMutation() { export function useSetVerificationPrefsMutation() {
const ax = useAnalytics() const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const client = usePdsClient()
return useMutation<void, unknown, AppBskyActorDefs.VerificationPrefs>({ return useMutation<void, unknown, AppBskyActorDefs.VerificationPrefs>({
mutationFn: async prefs => { mutationFn: async prefs => {
await agent.setVerificationPrefs(prefs) await client.call(setVerificationPrefs, prefs)
if (prefs.hideBadges) { if (prefs.hideBadges) {
ax.metric('verification:settings:hideBadges', {}) ax.metric('verification:settings:hideBadges', {})
} else { } else {
+75 -60
View File
@@ -3,17 +3,24 @@ import {
type AppBskyActorDefs, type AppBskyActorDefs,
type AppBskyActorGetProfile, type AppBskyActorGetProfile,
type AppBskyActorGetProfiles, type AppBskyActorGetProfiles,
type AppBskyActorProfile,
type AppBskyGraphGetFollows, type AppBskyGraphGetFollows,
type AtpAgent,
AtUri, AtUri,
type Un$Typed, type Un$Typed,
} from '@atproto/api' } from '@atproto/api'
import {type Client} from '@atproto/lex'
import { import {
type AtIdentifierString, type AtIdentifierString,
type AtUriString,
type DidString, type DidString,
toDatetimeString, toDatetimeString,
} from '@atproto/syntax' } from '@atproto/syntax'
import {
deleteFollow,
follow,
muteActor,
unmuteActor,
upsertProfile,
} from '@bsky.app/sdk'
import { import {
type InfiniteData, type InfiniteData,
keepPreviousData, keepPreviousData,
@@ -24,7 +31,6 @@ import {
} from '@tanstack/react-query' } from '@tanstack/react-query'
import {uploadBlob} from '#/lib/api' import {uploadBlob} from '#/lib/api'
import {toLegacyBlobRef} from '#/lib/api/legacy-blob'
import {until} from '#/lib/async/until' import {until} from '#/lib/async/until'
import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue' import {useToggleMutationQueue} from '#/lib/hooks/useToggleMutationQueue'
import {updateProfileShadow} from '#/state/cache/profile-shadow' import {updateProfileShadow} from '#/state/cache/profile-shadow'
@@ -38,7 +44,7 @@ import {
useUnstableProfileViewCache, useUnstableProfileViewCache,
} from '#/state/queries/unstable-profile-cache' } from '#/state/queries/unstable-profile-cache'
import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache' import {useUpdateProfileVerificationCache} from '#/state/queries/verification/useUpdateProfileVerificationCache'
import {useAgent, usePdsClient, useSession} from '#/state/session' import {useAppviewClient, usePdsClient, useSession} from '#/state/session'
import * as userActionHistory from '#/state/userActionHistory' import * as userActionHistory from '#/state/userActionHistory'
import {useAnalytics} from '#/analytics' import {useAnalytics} from '#/analytics'
import {type Metrics, toClout} from '#/analytics/metrics' import {type Metrics, toClout} from '#/analytics/metrics'
@@ -74,7 +80,7 @@ export function useProfileQuery({
did: string | undefined did: string | undefined
staleTime?: number staleTime?: number
}) { }) {
const agent = useAgent() const client = useAppviewClient()
const {getUnstableProfile} = useUnstableProfileViewCache() const {getUnstableProfile} = useUnstableProfileViewCache()
return useQuery<AppBskyActorDefs.ProfileViewDetailed>({ return useQuery<AppBskyActorDefs.ProfileViewDetailed>({
// WARNING // WARNING
@@ -85,8 +91,9 @@ export function useProfileQuery({
refetchOnWindowFocus: true, refetchOnWindowFocus: true,
queryKey: RQKEY(did ?? ''), queryKey: RQKEY(did ?? ''),
queryFn: async () => { queryFn: async () => {
const res = await agent.getProfile({actor: did ?? ''}) return await client.call(app.bsky.actor.getProfile, {
return res.data actor: (did ?? '') as AtIdentifierString,
})
}, },
placeholderData: () => { placeholderData: () => {
if (!did) return if (!did) return
@@ -103,21 +110,22 @@ export function useProfilesQuery({
handles: string[] handles: string[]
maintainData?: boolean maintainData?: boolean
}) { }) {
const agent = useAgent() const client = useAppviewClient()
return useQuery({ return useQuery({
enabled: handles.length > 0, enabled: handles.length > 0,
staleTime: STALE.MINUTES.FIVE, staleTime: STALE.MINUTES.FIVE,
queryKey: profilesQueryKey(handles), queryKey: profilesQueryKey(handles),
queryFn: async () => { queryFn: async () => {
const res = await agent.getProfiles({actors: handles}) return await client.call(app.bsky.actor.getProfiles, {
return res.data actors: handles as AtIdentifierString[],
})
}, },
placeholderData: maintainData ? keepPreviousData : undefined, placeholderData: maintainData ? keepPreviousData : undefined,
}) })
} }
export function usePrefetchProfileQuery() { export function usePrefetchProfileQuery() {
const agent = useAgent() const client = useAppviewClient()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const prefetchProfileQuery = useCallback( const prefetchProfileQuery = useCallback(
async (did: string) => { async (did: string) => {
@@ -125,12 +133,13 @@ export function usePrefetchProfileQuery() {
staleTime: STALE.SECONDS.THIRTY, staleTime: STALE.SECONDS.THIRTY,
queryKey: RQKEY(did), queryKey: RQKEY(did),
queryFn: async () => { queryFn: async () => {
const res = await agent.getProfile({actor: did || ''}) return await client.call(app.bsky.actor.getProfile, {
return res.data actor: (did || '') as AtIdentifierString,
})
}, },
}) })
}, },
[queryClient, agent], [queryClient, client],
) )
return prefetchProfileQuery return prefetchProfileQuery
} }
@@ -138,18 +147,18 @@ export function usePrefetchProfileQuery() {
interface ProfileUpdateParams { interface ProfileUpdateParams {
profile: AppBskyActorDefs.ProfileViewDetailed profile: AppBskyActorDefs.ProfileViewDetailed
updates: updates:
| Un$Typed<AppBskyActorProfile.Record> | Un$Typed<app.bsky.actor.profile.Main>
| (( | ((
existing: Un$Typed<AppBskyActorProfile.Record>, existing: Un$Typed<app.bsky.actor.profile.Main>,
) => Un$Typed<AppBskyActorProfile.Record>) ) => Un$Typed<app.bsky.actor.profile.Main>)
newUserAvatar?: ImageMeta | undefined | null newUserAvatar?: ImageMeta | undefined | null
newUserBanner?: ImageMeta | undefined | null newUserBanner?: ImageMeta | undefined | null
checkCommitted?: (res: AppBskyActorGetProfile.Response) => boolean checkCommitted?: (res: AppBskyActorGetProfile.Response) => boolean
} }
export function useProfileUpdateMutation() { export function useProfileUpdateMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent()
const pdsClient = usePdsClient() const pdsClient = usePdsClient()
const appviewClient = useAppviewClient()
const updateProfileVerificationCache = useUpdateProfileVerificationCache() const updateProfileVerificationCache = useUpdateProfileVerificationCache()
return useMutation<void, Error, ProfileUpdateParams>({ return useMutation<void, Error, ProfileUpdateParams>({
mutationFn: async ({ mutationFn: async ({
@@ -175,8 +184,8 @@ export function useProfileUpdateMutation() {
newUserBanner.mime, newUserBanner.mime,
) )
} }
await agent.upsertProfile(async existing => { await pdsClient.call(upsertProfile, async existing => {
let next: Un$Typed<AppBskyActorProfile.Record> = existing || {} let next: Un$Typed<app.bsky.actor.profile.Main> = existing || {}
if (typeof updates === 'function') { if (typeof updates === 'function') {
next = updates(next) next = updates(next)
} else { } else {
@@ -188,20 +197,20 @@ export function useProfileUpdateMutation() {
} }
if (newUserAvatarPromise) { if (newUserAvatarPromise) {
const res = await newUserAvatarPromise const res = await newUserAvatarPromise
next.avatar = toLegacyBlobRef(res.blob) next.avatar = res.blob
} else if (newUserAvatar === null) { } else if (newUserAvatar === null) {
next.avatar = undefined next.avatar = undefined
} }
if (newUserBannerPromise) { if (newUserBannerPromise) {
const res = await newUserBannerPromise const res = await newUserBannerPromise
next.banner = toLegacyBlobRef(res.blob) next.banner = res.blob
} else if (newUserBanner === null) { } else if (newUserBanner === null) {
next.banner = undefined next.banner = undefined
} }
return next return next
}) })
await whenAppViewReady( await whenAppViewReady(
agent, appviewClient,
profile.did, profile.did,
checkCommitted || checkCommitted ||
(res => { (res => {
@@ -252,7 +261,7 @@ export function useProfileFollowMutationQueue(
position?: number, position?: number,
contextProfileDid?: string, contextProfileDid?: string,
) { ) {
const agent = useAgent() const client = useAppviewClient()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const did = profile.did const did = profile.did
@@ -333,12 +342,12 @@ export function useProfileFollowMutationQueue(
} }
if (finalFollowingUri) { if (finalFollowingUri) {
void agent.app.bsky.graph void client
.getSuggestedFollowsByActor({ .call(app.bsky.graph.getSuggestedFollowsByActor, {
actor: did, actor: did as AtIdentifierString,
}) })
.then(res => { .then(res => {
const dids = res.data.suggestions const dids = res.suggestions
.filter(a => !a.viewer?.following) .filter(a => !a.viewer?.following)
.map(a => a.did) .map(a => a.did)
.slice(0, 8) .slice(0, 8)
@@ -375,7 +384,7 @@ function useProfileFollowMutation(
) { ) {
const ax = useAnalytics() const ax = useAnalytics()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const agent = useAgent() const pdsClient = usePdsClient()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {captureAction} = useProgressGuideControls() const {captureAction} = useProgressGuideControls()
@@ -400,7 +409,7 @@ function useProfileFollowMutation(
position, position,
contextProfileDid, contextProfileDid,
}) })
return await agent.follow(did) return await pdsClient.call(follow, {did: did as DidString})
}, },
}) })
} }
@@ -409,11 +418,11 @@ function useProfileUnfollowMutation(
logContext: Metrics['profile:unfollow']['logContext'], logContext: Metrics['profile:unfollow']['logContext'],
) { ) {
const ax = useAnalytics() const ax = useAnalytics()
const agent = useAgent() const pdsClient = usePdsClient()
return useMutation<void, Error, {did: string; followUri: string}>({ return useMutation<void, Error, {did: string; followUri: string}>({
mutationFn: async ({followUri}) => { mutationFn: async ({followUri}) => {
ax.metric('profile:unfollow', {logContext}) ax.metric('profile:unfollow', {logContext})
return await agent.deleteFollow(followUri) return await pdsClient.call(deleteFollow, followUri as AtUriString)
}, },
}) })
} }
@@ -423,7 +432,7 @@ export function useProfileMuteMutationQueue(
) { ) {
const ax = useAnalytics() const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const did = profile.did const did = profile.did as DidString
const initialMuted = profile.viewer?.muted const initialMuted = profile.viewer?.muted
const muteMutation = useProfileMuteMutation() const muteMutation = useProfileMuteMutation()
const unmuteMutation = useProfileUnmuteMutation() const unmuteMutation = useProfileUnmuteMutation()
@@ -432,15 +441,11 @@ export function useProfileMuteMutationQueue(
initialState: initialMuted, initialState: initialMuted,
runMutation: async (_prevMuted, shouldMute) => { runMutation: async (_prevMuted, shouldMute) => {
if (shouldMute) { if (shouldMute) {
await muteMutation.mutateAsync({ await muteMutation.mutateAsync({did})
did,
})
ax.metric('profile:mute', {}) ax.metric('profile:mute', {})
return true return true
} else { } else {
await unmuteMutation.mutateAsync({ await unmuteMutation.mutateAsync({did})
did,
})
ax.metric('profile:unmute', {}) ax.metric('profile:unmute', {})
return false return false
} }
@@ -485,7 +490,7 @@ export function useProfileMuteRepostsMutationQueue(
) { ) {
const ax = useAnalytics() const ax = useAnalytics()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const did = profile.did const did = profile.did as DidString
const initialMutedOnlyReposts = !!profile.viewer?.mutedOnlyReposts const initialMutedOnlyReposts = !!profile.viewer?.mutedOnlyReposts
const muteRepostsMutation = useProfileMuteRepostsMutation() const muteRepostsMutation = useProfileMuteRepostsMutation()
const unmuteMutation = useProfileUnmuteMutation() const unmuteMutation = useProfileUnmuteMutation()
@@ -494,15 +499,11 @@ export function useProfileMuteRepostsMutationQueue(
initialState: initialMutedOnlyReposts, initialState: initialMutedOnlyReposts,
runMutation: async (_prevMutedOnlyReposts, shouldMute) => { runMutation: async (_prevMutedOnlyReposts, shouldMute) => {
if (shouldMute) { if (shouldMute) {
await muteRepostsMutation.mutateAsync({ await muteRepostsMutation.mutateAsync({did})
did,
})
ax.metric('profile:muteReposts', {}) ax.metric('profile:muteReposts', {})
return true return true
} else { } else {
await unmuteMutation.mutateAsync({ await unmuteMutation.mutateAsync({did})
did,
})
ax.metric('profile:unmuteReposts', {}) ax.metric('profile:unmuteReposts', {})
return false return false
} }
@@ -536,10 +537,10 @@ export function useProfileMuteRepostsMutationQueue(
function useProfileMuteMutation() { function useProfileMuteMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const appviewClient = useAppviewClient()
return useMutation<void, Error, {did: string}>({ return useMutation({
mutationFn: async ({did}) => { mutationFn: async ({did}: {did: DidString}) => {
await agent.mute(did) await appviewClient.call(muteActor, {actor: did})
}, },
onSuccess() { onSuccess() {
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()}) void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
@@ -549,10 +550,14 @@ function useProfileMuteMutation() {
function useProfileMuteRepostsMutation() { function useProfileMuteRepostsMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const appviewClient = useAppviewClient()
return useMutation<void, Error, {did: string}>({ return useMutation({
mutationFn: async ({did}) => { mutationFn: async ({did}: {did: DidString}) => {
await agent.mute(did, {onlyReposts: true}) await appviewClient.call(muteActor, {
actor: did,
// @ts-expect-error missing from this SDK version, remove this
onlyReposts: true,
})
}, },
onSuccess() { onSuccess() {
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()}) void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
@@ -562,10 +567,10 @@ function useProfileMuteRepostsMutation() {
function useProfileUnmuteMutation() { function useProfileUnmuteMutation() {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const agent = useAgent() const appviewClient = useAppviewClient()
return useMutation<void, Error, {did: string}>({ return useMutation({
mutationFn: async ({did}) => { mutationFn: async ({did}: {did: DidString}) => {
await agent.unmute(did) await appviewClient.call(unmuteActor, {actor: did})
}, },
onSuccess() { onSuccess() {
void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()}) void queryClient.invalidateQueries({queryKey: RQKEY_MY_MUTED()})
@@ -679,7 +684,7 @@ function useProfileUnblockMutation() {
} }
async function whenAppViewReady( async function whenAppViewReady(
agent: AtpAgent, client: Client,
actor: string, actor: string,
fn: (res: AppBskyActorGetProfile.Response) => boolean, fn: (res: AppBskyActorGetProfile.Response) => boolean,
) { ) {
@@ -687,7 +692,17 @@ async function whenAppViewReady(
5, // 5 tries 5, // 5 tries
1e3, // 1s delay between tries 1e3, // 1s delay between tries
fn, fn,
() => agent.app.bsky.actor.getProfile({actor}), /*
* `checkCommitted` callbacks are still written against the legacy
* `Response` envelope, so wrap the lex output until those consumers move.
*/
async () => ({
success: true,
headers: {},
data: await client.call(app.bsky.actor.getProfile, {
actor: actor as AtIdentifierString,
}),
}),
) )
} }
+28 -22
View File
@@ -1,7 +1,12 @@
import {type AppBskyActorProfile, type Un$Typed} from '@atproto/api'
import {TID} from '@atproto/common-web' import {TID} from '@atproto/common-web'
import {type Client} from '@atproto/lex' import {type Client} from '@atproto/lex'
import {PasswordSession} from '@atproto/lex-password-session' import {PasswordSession} from '@atproto/lex-password-session'
import {toDatetimeString} from '@atproto/syntax'
import {
overwriteSavedFeeds,
setPersonalDetails,
upsertProfile,
} from '@bsky.app/sdk'
import {networkRetry} from '#/lib/async/retry' import {networkRetry} from '#/lib/async/retry'
import { import {
@@ -21,8 +26,8 @@ import {
} from '#/ageAssurance/data' } from '#/ageAssurance/data'
import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state' import {unsafeGetAndComputeAgeAssurance} from '#/ageAssurance/state'
import {features} from '#/analytics' import {features} from '#/analytics'
import {type BskyAppAgent} from './bridge-agent' import {type app} from '#/lexicons'
import {agentToPdsClient} from './clients' import {agentToAppviewClient, agentToPdsClient} from './clients'
import {configureModerationForAccount} from './moderation' import {configureModerationForAccount} from './moderation'
import { import {
buildBundle, buildBundle,
@@ -88,7 +93,7 @@ export async function createSessionBundleAndCreateAccount(
const gates = features.refresh({strategy: 'prefer-fresh-gates'}) const gates = features.refresh({strategy: 'prefer-fresh-gates'})
configureModerationForAccount(bundle.agent, earlyAccount) configureModerationForAccount(bundle.agent, earlyAccount)
const createdAt = new Date().toISOString() const createdAt = toDatetimeString(new Date())
const birthdate = birthDate.toISOString() const birthdate = birthDate.toISOString()
/* /*
@@ -100,22 +105,23 @@ export async function createSessionBundleAndCreateAccount(
setCreatedAtForDid({did: earlyAccount.did, createdAt}) setCreatedAtForDid({did: earlyAccount.did, createdAt})
setBirthdateForDid({did: earlyAccount.did, birthdate}) setBirthdateForDid({did: earlyAccount.did, birthdate})
snoozeBirthdateUpdateAllowedForDid(earlyAccount.did) snoozeBirthdateUpdateAllowedForDid(earlyAccount.did)
// 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. // Start the prefetch after seeding its synchronous birthdate inputs.
const aa = prefetchAgeAssuranceServerData({agent: bundle.agent}) const aa = prefetchAgeAssuranceServerData({
appviewClient: agentToAppviewClient(bundle.agent),
accountClient: pdsClient,
})
const isProd = Boolean(IS_PROD_SERVICE(service)) const isProd = Boolean(IS_PROD_SERVICE(service))
const postSignupTasks: Promise<unknown>[] = [ const postSignupTasks: Promise<unknown>[] = [
savePersonalDetails(bundle.agent, birthdate), savePersonalDetails(pdsClient, birthDate),
initializeProfile(bundle.agent, {handle, createdAt, isProd}), initializeProfile(pdsClient, {handle, createdAt, isProd}),
] ]
if (isProd) { if (isProd) {
postSignupTasks.push( postSignupTasks.push(
initializeSavedFeeds(bundle.agent), initializeSavedFeeds(pdsClient),
restrictChatAfterAgeAssurance( restrictChatAfterAgeAssurance(aa, pdsClient, earlyAccount.did),
aa,
agentToPdsClient(bundle.agent),
earlyAccount.did,
),
) )
} }
// Post-signup writes are not required to enter onboarding. // Post-signup writes are not required to enter onboarding.
@@ -170,41 +176,41 @@ function snapshotNewAccount(
} }
} }
function savePersonalDetails(agent: BskyAppAgent, birthDate: string) { function savePersonalDetails(client: Client, birthDate: Date) {
return retryPostSignupTask('set birthDate', 3, () => return retryPostSignupTask('set birthDate', 3, () =>
agent.setPersonalDetails({birthDate}), client.call(setPersonalDetails, {birthDate}),
) )
} }
function initializeProfile( function initializeProfile(
agent: BskyAppAgent, client: Client,
{ {
handle, handle,
createdAt, createdAt,
isProd, isProd,
}: { }: {
handle: string handle: string
createdAt: string createdAt: ReturnType<typeof toDatetimeString>
isProd: boolean isProd: boolean
}, },
) { ) {
return retryPostSignupTask('set initial profile', 3, () => return retryPostSignupTask('set initial profile', 3, () =>
agent.upsertProfile(prev => { client.call(upsertProfile, prev => {
const next: Un$Typed<AppBskyActorProfile.Record> = prev || {} const next: Partial<app.bsky.actor.profile.Main> = prev || {}
if (isProd) { if (isProd) {
next.displayName = handle next.displayName = handle
next.createdAt = createdAt next.createdAt = createdAt
} else { } else {
next.createdAt = prev?.createdAt || new Date().toISOString() next.createdAt = prev?.createdAt || toDatetimeString(new Date())
} }
return next return next
}), }),
) )
} }
function initializeSavedFeeds(agent: BskyAppAgent) { function initializeSavedFeeds(client: Client) {
return retryPostSignupTask('set initial feeds', 1, () => return retryPostSignupTask('set initial feeds', 1, () =>
agent.overwriteSavedFeeds([ client.call(overwriteSavedFeeds, [
{...DISCOVER_SAVED_FEED, id: TID.nextStr()}, {...DISCOVER_SAVED_FEED, id: TID.nextStr()},
{...TIMELINE_SAVED_FEED, id: TID.nextStr()}, {...TIMELINE_SAVED_FEED, id: TID.nextStr()},
]), ]),
+9 -2
View File
@@ -13,6 +13,7 @@ import {
createPublicAgent, createPublicAgent,
PasswordSessionManager, PasswordSessionManager,
} from './bridge-agent' } from './bridge-agent'
import {agentToAppviewClient, agentToPdsClient} from './clients'
import {addSessionErrorLog} from './logging' import {addSessionErrorLog} from './logging'
import {configureModerationForAccount} from './moderation' import {configureModerationForAccount} from './moderation'
import {networkAwareFetch} from './network' import {networkAwareFetch} from './network'
@@ -290,7 +291,10 @@ export async function createSessionBundleAndResume(
) ?? storedAccount ) ?? storedAccount
configureModerationForAccount(bundle.agent, earlyAccount) 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 * 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'}) const gates = features.refresh({strategy: 'prefer-fresh-gates'})
configureModerationForAccount(bundle.agent, earlyAccount) 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()) bundle.agent.configureProxy(BLUESKY_PROXY_HEADER.get())