diff --git a/src/ageAssurance/components/RedirectOverlay.tsx b/src/ageAssurance/components/RedirectOverlay.tsx index 5a6f5b2934..5b31e94365 100644 --- a/src/ageAssurance/components/RedirectOverlay.tsx +++ b/src/ageAssurance/components/RedirectOverlay.tsx @@ -16,7 +16,7 @@ import {Trans} from '@lingui/react/macro' import {retry} from '#/lib/async/retry' import {wait} from '#/lib/async/wait' import {parseLinkingUrl} from '#/lib/parseLinkingUrl' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, useSession} from '#/state/session' import {atoms as a, platform, useBreakpoints, useTheme} from '#/alf' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {Button, ButtonText} from '#/components/Button' @@ -176,7 +176,7 @@ function Inner() { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() - const agent = useAgent() + const appviewClient = useAppviewClient() const polling = useRef(false) const unmounted = useRef(false) const [error, setError] = useState(false) @@ -196,10 +196,10 @@ function Inner() { 5, () => true, async () => { - if (!agent.session) return + if (!appviewClient.did) return if (unmounted.current) return - const data = await refetchAgeAssuranceServerState({agent}) + const data = await refetchAgeAssuranceServerState({appviewClient}) if (data?.state.status !== 'assured') { throw new Error( @@ -214,7 +214,7 @@ function Inner() { ) .then(async data => { if (!data) return - if (!agent.session) return + if (!appviewClient.did) return if (unmounted.current) return setSuccess(true) @@ -230,7 +230,7 @@ function Inner() { return () => { unmounted.current = true } - }, [ax, agent]) + }, [ax, appviewClient]) if (success) { return ( diff --git a/src/ageAssurance/data.tsx b/src/ageAssurance/data.tsx index 9cf6703dfc..2beac738c2 100644 --- a/src/ageAssurance/data.tsx +++ b/src/ageAssurance/data.tsx @@ -7,6 +7,8 @@ import { AtpAgent, type ChatBskyActorDeclaration, } from '@atproto/api' +import {type Client} from '@atproto/lex' +import {getPreferences} from '@bsky.app/sdk' import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' import {focusManager, QueryClient, useQuery} from '@tanstack/react-query' import {persistQueryClient} from '@tanstack/react-query-persist-client' @@ -21,7 +23,7 @@ import { snoozeBirthdateUpdateAllowedForDid, } from '#/state/birthdate' import {fetchActorDeclarationRecord} from '#/state/queries/messages/actor-declaration' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, usePdsClient, useSession} from '#/state/session' import {DEVICE_SIGNALS_SUPPORTED} from '#/ageAssurance/const' import * as debug from '#/ageAssurance/debug' import {logger} from '#/ageAssurance/logger' @@ -37,6 +39,7 @@ import { } from '#/ageAssurance/util' import {IS_DEV} from '#/env' import {useGeolocation} from '#/geolocation' +import {app} from '#/lexicons' import {device} from '#/storage' /** @@ -63,12 +66,6 @@ const [, cacheHydrationPromise] = persistQueryClient({ persister, }) -export function getDidFromAgentSession(agent: AtpAgent) { - const sessionManager = agent.sessionManager - if (!sessionManager || !sessionManager.did) return - return sessionManager.did -} - /* * Optimistic data */ @@ -187,7 +184,7 @@ export function useConfigQuery() { export function createServerStateQueryKey({did}: {did: string}) { return ['serverState', did] } -export async function getServerState({agent}: {agent: AtpAgent}) { +export async function getServerState({appviewClient}: {appviewClient: Client}) { if (debug.enabled && debug.serverState) return debug.resolve(debug.serverState) const geolocation = device.get(['mergedGeolocation']) @@ -195,17 +192,21 @@ export async function getServerState({agent}: {agent: AtpAgent}) { logger.error(`getServerState: missing geolocation countryCode`) return null } - const {data} = await agent.app.bsky.ageassurance.getState({ + const data = await appviewClient.call(app.bsky.ageassurance.getState, { countryCode: geolocation.countryCode, regionCode: geolocation.regionCode, }) - const did = getDidFromAgentSession(agent) + const did = appviewClient.did if (data && did && createdAtCache.has(did)) { /* * If account was just created, just use the local cache if available. On - * subsequent reloads, the server should have the correct value. + * subsequent reloads, the server should have the correct value. The cache + * holds ISO datetime strings (written from `new Date().toISOString()`), so + * assert the branded DatetimeString at this boundary. */ - data.metadata.accountCreatedAt = createdAtCache.get(did) + data.metadata.accountCreatedAt = createdAtCache.get( + did, + ) as typeof data.metadata.accountCreatedAt } return data ?? null } @@ -218,8 +219,12 @@ export function getServerStateFromCache({ createServerStateQueryKey({did}), ) } -export async function prefetchServerState({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) +export async function prefetchServerState({ + appviewClient, +}: { + appviewClient: Client +}) { + const did = appviewClient.did if (!did) return @@ -234,7 +239,7 @@ export async function prefetchServerState({agent}: {agent: AtpAgent}) { try { logger.debug(`prefetchServerState: resolving...`) - const res = await networkRetry(3, () => getServerState({agent})) + const res = await networkRetry(3, () => getServerState({appviewClient})) if (res) { qc.setQueryData(qk, res) } @@ -245,11 +250,15 @@ export async function prefetchServerState({agent}: {agent: AtpAgent}) { }) } } -export async function refetchServerState({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) +export async function refetchServerState({ + appviewClient, +}: { + appviewClient: Client +}) { + const did = appviewClient.did if (!did) return logger.debug(`refetchServerState: fetching...`) - const res = await networkRetry(3, () => getServerState({agent})) + const res = await networkRetry(3, () => getServerState({appviewClient})) if (res) { qc.setQueryData( createServerStateQueryKey({did}), @@ -279,8 +288,8 @@ export function usePatchServerState() { ) } export function useServerStateQuery() { - const agent = useAgent() - const did = getDidFromAgentSession(agent) + const appviewClient = useAppviewClient() + const did = appviewClient.did const query = useQuery( { enabled: !!did, @@ -290,7 +299,7 @@ export function useServerStateQuery() { }, queryKey: createServerStateQueryKey({did: did!}), async queryFn() { - return getServerState({agent}) + return getServerState({appviewClient}) }, }, qc, @@ -342,15 +351,15 @@ export function createOtherRequiredDataQueryKey({did}: {did: string}) { return ['otherRequiredData', did] } async function getOtherRequiredData({ - agent, + accountClient, }: { - agent: AtpAgent + accountClient: Client }): Promise { if (debug.enabled) return debug.resolve(debug.otherRequiredData) - const did = getDidFromAgentSession(agent) + const did = accountClient.did const [prefs, actorDeclaration] = await Promise.all([ - agent.getPreferences(), - fetchActorDeclarationRecord({did, agent}), + accountClient.call(getPreferences), + fetchActorDeclarationRecord({did, client: accountClient}), ]) const data: OtherRequiredData = { birthdate: prefs.birthDate ? prefs.birthDate.toISOString() : undefined, @@ -426,8 +435,12 @@ export function setOtherRequiredDataActorDeclarationCache({ next, ) } -export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) +export async function prefetchOtherRequiredData({ + accountClient, +}: { + accountClient: Client +}) { + const did = accountClient.did if (!did) return @@ -442,7 +455,9 @@ export async function prefetchOtherRequiredData({agent}: {agent: AtpAgent}) { try { logger.debug(`prefetchOtherRequiredData: resolving...`) - const res = await networkRetry(3, () => getOtherRequiredData({agent})) + const res = await networkRetry(3, () => + getOtherRequiredData({accountClient}), + ) qc.setQueryData(qk, res) } catch (err) { const e = err as Error @@ -471,8 +486,8 @@ export function usePatchOtherRequiredData() { ) } export function useOtherRequiredDataQuery() { - const agent = useAgent() - const did = getDidFromAgentSession(agent) + const accountClient = usePdsClient() + const did = accountClient.did return useQuery( { enabled: !!did, @@ -482,7 +497,7 @@ export function useOtherRequiredDataQuery() { }, queryKey: createOtherRequiredDataQueryKey({did: did!}), async queryFn() { - return getOtherRequiredData({agent}) + return getOtherRequiredData({accountClient}) }, }, qc, @@ -577,8 +592,12 @@ export function setDeviceSignalsForRegion({ prev => ({...prev, [regionKey]: signals}), ) } -export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) { - const did = getDidFromAgentSession(agent) +export async function prefetchDeviceSignals({ + appviewClient, +}: { + appviewClient: Client +}) { + const did = appviewClient.did if (!did) return /** @@ -613,8 +632,8 @@ export async function prefetchDeviceSignals({agent}: {agent: AtpAgent}) { */ } export function useDeviceSignalsQuery() { - const agent = useAgent() - const did = getDidFromAgentSession(agent) + const appviewClient = useAppviewClient() + const did = appviewClient.did const {data: config} = useConfigQuery() const geolocation = useGeolocation() /* @@ -659,13 +678,19 @@ export function useDeviceSignalsQuery() { /** * Helper to prefetch all age assurance data from the server. */ -export function prefetchAgeAssuranceServerData({agent}: {agent: AtpAgent}) { +export function prefetchAgeAssuranceServerData({ + appviewClient, + accountClient, +}: { + appviewClient: Client + accountClient: Client +}) { return Promise.allSettled([ // config fetch initiated at the top of the App.platform.tsx files, awaited here configPrefetchPromise, - prefetchServerState({agent}), - prefetchOtherRequiredData({agent}), - prefetchDeviceSignals({agent}), + prefetchServerState({appviewClient}), + prefetchOtherRequiredData({accountClient}), + prefetchDeviceSignals({appviewClient}), ]) } diff --git a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx index ad584ca203..acf20f38c5 100644 --- a/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx +++ b/src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx @@ -6,7 +6,7 @@ import {Trans} from '@lingui/react/macro' import {retry} from '#/lib/async/retry' import {wait} from '#/lib/async/wait' -import {useAgent} from '#/state/session' +import {useAppviewClient} from '#/state/session' import {atoms as a, useTheme, web} from '#/alf' import {AgeAssuranceBadge} from '#/components/ageAssurance/AgeAssuranceBadge' import {Button, ButtonText} from '#/components/Button' @@ -84,7 +84,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { const t = useTheme() const ax = useAnalytics() const {_} = useLingui() - const agent = useAgent() + const appviewClient = useAppviewClient() const polling = useRef(false) const unmounted = useRef(false) const control = useAgeAssuranceRedirectDialogControl() @@ -104,10 +104,10 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { 5, () => true, async () => { - if (!agent.session) return + if (!appviewClient.did) return if (unmounted.current) return - const data = await refetchAgeAssuranceServerState({agent}) + const data = await refetchAgeAssuranceServerState({appviewClient}) if (data?.state.status !== 'assured') { throw new Error( @@ -122,7 +122,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { ) .then(async data => { if (!data) return - if (!agent.session) return + if (!appviewClient.did) return if (unmounted.current) return setSuccess(true) @@ -138,7 +138,7 @@ export function Inner({}: {optimisticState?: AgeAssuranceRedirectDialogState}) { return () => { unmounted.current = true } - }, [ax, agent, control]) + }, [ax, appviewClient, control]) if (success) { return ( diff --git a/src/components/contacts/screens/GetContacts.tsx b/src/components/contacts/screens/GetContacts.tsx index d84e0d1ab0..de5d6cf98b 100644 --- a/src/components/contacts/screens/GetContacts.tsx +++ b/src/components/contacts/screens/GetContacts.tsx @@ -2,24 +2,21 @@ import {useContext} from 'react' import {Alert, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import * as Contacts from 'expo-contacts' -import type AtpAgent from '@atproto/api' -import { - type AppBskyActorProfile, - AppBskyContactImportContacts, - type Un$Typed, -} from '@atproto/api' +import {type Un$Typed} 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 {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useMutation, useQueryClient} from '@tanstack/react-query' import {uploadBlob} from '#/lib/api' -import {toLegacyBlobRef} from '#/lib/api/legacy-blob' import {cleanError, isNetworkError} from '#/lib/strings/errors' +import {matchXrpcError} from '#/lib/xrpc-error' import {logger} from '#/logger' import {findContactsStatusQueryKey} from '#/state/queries/find-contacts' -import {useAgent, usePdsClient} from '#/state/session' +import {useAppviewClient, usePdsClient} from '#/state/session' import { Context as OnboardingContext, type OnboardingAction, @@ -32,6 +29,7 @@ import {Loader} from '#/components/Loader' import * as Toast from '#/components/Toast' import {Text} from '#/components/Typography' import {useAnalytics} from '#/analytics' +import {app} from '#/lexicons' import { contactsWithPhoneNumbersOnly, filterMatchedNumbers, @@ -56,8 +54,8 @@ export function GetContacts({ }) { const {_} = useLingui() const ax = useAnalytics() - const agent = useAgent() const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const insets = useSafeAreaInsets() const gutters = useGutters([0, 'wide']) const queryClient = useQueryClient() @@ -75,7 +73,7 @@ export function GetContacts({ */ if (context === 'Onboarding' && maybeOnboardingContext) { try { - await createProfileRecord(agent, pdsClient, maybeOnboardingContext) + await createProfileRecord(pdsClient, maybeOnboardingContext) } catch (error) { logger.debug('Error creating profile record:', {safeMessage: error}) } @@ -88,13 +86,13 @@ export function GetContacts({ ) if (phoneNumbers.length > 0) { - const res = await agent.app.bsky.contact.importContacts({ + const res = await appviewClient.call(app.bsky.contact.importContacts, { token: state.token, contacts: phoneNumbers.slice(0, MAX_UPLOAD_COUNT), }) return { - matches: res.data.matchesAndContactIndexes, + matches: res.matchesAndContactIndexes, indexToContactId, } } else { @@ -151,29 +149,30 @@ export function GetContacts({ ), {type: 'error'}, ) - } else if ( - err instanceof AppBskyContactImportContacts.TooManyContactsError - ) { - Toast.show( - _( - msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`, - ), - {type: 'error'}, - ) - } else if ( - err instanceof AppBskyContactImportContacts.InvalidTokenError - ) { - Toast.show( - _( - msg`Could not upload contacts. You need to re-verify your phone number to proceed`, - ), - {type: 'error'}, - ) - } else { - logger.error('Error uploading contacts', {safeMessage: err}) - Toast.show(_(msg`Could not upload contacts. ${cleanError(err)}`), { - type: 'error', - }) + return + } + switch (matchXrpcError(err, app.bsky.contact.importContacts)) { + case 'TooManyContacts': + Toast.show( + _( + msg`Too many contacts - you've exceeded the number of contacts you can import to find your friends`, + ), + {type: 'error'}, + ) + break + case 'InvalidToken': + Toast.show( + _( + msg`Could not upload contacts. You need to re-verify your phone number to proceed`, + ), + {type: 'error'}, + ) + break + default: + logger.error('Error uploading contacts', {safeMessage: err}) + Toast.show(_(msg`Could not upload contacts. ${cleanError(err)}`), { + type: 'error', + }) } }, }) @@ -328,7 +327,6 @@ function showPermissionDeniedAlert() { * Copied from `#/screens/Onboarding/StepFinished/index.tsx` */ async function createProfileRecord( - agent: AtpAgent, pdsClient: Client, onboardingContext: { state: OnboardingState @@ -342,19 +340,21 @@ async function createProfileRecord( ? uploadBlob(pdsClient, imageUri, imageMime) : undefined - await agent.upsertProfile(async existing => { - let next: Un$Typed = existing ?? {} + await pdsClient.call(upsertProfile, async existing => { + let next: Un$Typed = existing ?? {} if (blobPromise) { const res = await blobPromise if (res.blob) { - next.avatar = toLegacyBlobRef(res.blob) + next.avatar = res.blob } } next.displayName = '' - next.createdAt = new Date().toISOString() + if (!next.createdAt) { + next.createdAt = toDatetimeString(new Date()) + } return next }) } diff --git a/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx b/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx index ceb07fb0e0..e6d14b2405 100644 --- a/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx +++ b/src/components/dialogs/lists/CreateListFromStarterPackDialog.tsx @@ -6,7 +6,6 @@ import { type AtIdentifierString, AtUri, type AtUriString, - type DidString, toDatetimeString, } from '@atproto/syntax' import {msg} from '@lingui/core/macro' @@ -86,8 +85,7 @@ export function CreateListFromStarterPackDialog({ items.map(item => { const listitemRecord: $Typed = { $type: 'app.bsky.graph.listitem', - // the list view is still legacy-typed, so its strings are unbranded - subject: item.subject.did as DidString, + subject: item.subject.did, list: listUri as AtUriString, createdAt: toDatetimeString(new Date()), } diff --git a/src/features/liveEvents/preferences.ts b/src/features/liveEvents/preferences.ts index 6fb2ac6d11..bd8cbbd645 100644 --- a/src/features/liveEvents/preferences.ts +++ b/src/features/liveEvents/preferences.ts @@ -1,12 +1,12 @@ 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 { preferencesQueryKey, usePreferencesQuery, } from '#/state/queries/preferences' -import {useAgent} from '#/state/session' +import {usePdsClient} from '#/state/session' import {useAnalytics} from '#/analytics' import * as env from '#/env' import {IS_WEB} from '#/env' @@ -14,10 +14,11 @@ import { type LiveEventFeed, type LiveEventFeedMetricContext, } from '#/features/liveEvents/types' +import {type app} from '#/lexicons' export type LiveEventPreferencesAction = Parameters< - Agent['updateLiveEventPreferences'] ->[0] & { + typeof updateLiveEventPreferences +>[1] & { /** * Flag that is internal to this hook, do not set when updating prefs */ @@ -38,7 +39,7 @@ export function useLiveEventPreferences() { function useWebOnlyDebugLiveEventPreferences() { const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() useEffect(() => { if (env.IS_DEV && IS_WEB && typeof window !== 'undefined') { @@ -46,14 +47,14 @@ function useWebOnlyDebugLiveEventPreferences() { window.__updateLiveEventPreferences = async ( action: LiveEventPreferencesAction, ) => { - await agent.updateLiveEventPreferences(action) + await pdsClient.call(updateLiveEventPreferences, action) // triggers a refetch await queryClient.invalidateQueries({ queryKey: preferencesQueryKey, }) } } - }, [agent, queryClient]) + }, [pdsClient, queryClient]) } export function useUpdateLiveEventPreferences(props: { @@ -65,10 +66,10 @@ export function useUpdateLiveEventPreferences(props: { }) { const ax = useAnalytics() const queryClient = useQueryClient() - const agent = useAgent() + const pdsClient = usePdsClient() return useMutation< - AppBskyActorDefs.LiveEventPreferences, + app.bsky.actor.defs.LiveEventPreferences, Error, LiveEventPreferencesAction, {undoAction: LiveEventPreferencesAction | null} @@ -108,10 +109,14 @@ export function useUpdateLiveEventPreferences(props: { } }, mutationFn: async action => { - const updated = await agent.updateLiveEventPreferences(action) - const prefs = updated.find(p => - asPredicate(AppBskyActorDefs.validateLiveEventPreferences)(p), - ) + /* + * The SDK action returns void, so after applying the update we read the + * 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) { case 'hideFeed': @@ -138,7 +143,7 @@ export function useUpdateLiveEventPreferences(props: { break } case 'toggleHideAllFeeds': { - if (prefs!.hideAllFeeds) { + if (prefs.hideAllFeeds) { ax.metric('liveEvents:hideAllFeedBanners', { context: props.metricContext, }) @@ -156,7 +161,7 @@ export function useUpdateLiveEventPreferences(props: { queryKey: preferencesQueryKey, }) - return prefs! + return prefs }, }) } diff --git a/src/lib/api/legacy-blob.ts b/src/lib/api/legacy-blob.ts deleted file mode 100644 index b60acadfe4..0000000000 --- a/src/lib/api/legacy-blob.ts +++ /dev/null @@ -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[0]) -} diff --git a/src/screens/Onboarding/StepFinished/index.tsx b/src/screens/Onboarding/StepFinished/index.tsx index 706a160003..9eb22efc70 100644 --- a/src/screens/Onboarding/StepFinished/index.tsx +++ b/src/screens/Onboarding/StepFinished/index.tsx @@ -1,21 +1,19 @@ import {useCallback, useState} from 'react' 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 {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 {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' import {useQueryClient} from '@tanstack/react-query' import {uploadBlob} from '#/lib/api' -import {toLegacyBlobRef} from '#/lib/api/legacy-blob' import { BSKY_APP_ACCOUNT_DID, DISCOVER_SAVED_FEED, @@ -28,7 +26,7 @@ import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-p import {getAllListMembers} from '#/state/queries/list-members' import {preferencesQueryKey} from '#/state/queries/preferences' 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 { useActiveStarterPack, @@ -50,6 +48,7 @@ import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/ico import {Loader} from '#/components/Loader' import {useAnalytics} from '#/analytics' import {IS_WEB} from '#/env' +import {app} from '#/lexicons' import * as bsky from '#/types/bsky' import {ValuePropositionPager} from './ValuePropositionPager' @@ -59,9 +58,8 @@ export function StepFinished() { const onboardDispatch = useOnboardingDispatch() const [saving, setSaving] = useState(false) const queryClient = useQueryClient() - const agent = useAgent() - const appviewClient = useAppviewClient() const pdsClient = usePdsClient() + const appviewClient = useAppviewClient() const requestNotificationsPermission = useRequestNotificationsPermission() const activeStarterPack = useActiveStarterPack() const setActiveStarterPack = useSetActiveStarterPack() @@ -71,15 +69,15 @@ export function StepFinished() { const finishOnboarding = useCallback(async () => { setSaving(true) - let starterPack: AppBskyGraphDefs.StarterPackView | undefined - let listItems: AppBskyGraphDefs.ListItemView[] | undefined + let starterPack: app.bsky.graph.defs.StarterPackView | undefined + let listItems: app.bsky.graph.defs.ListItemView[] | undefined if (activeStarterPack?.uri) { try { - const spRes = await agent.app.bsky.graph.getStarterPack({ - starterPack: activeStarterPack.uri, + const spRes = await appviewClient.call(app.bsky.graph.getStarterPack, { + starterPack: activeStarterPack.uri as AtUriString, }) - starterPack = spRes.data.starterPack + starterPack = spRes.starterPack } catch (e) { logger.error('Failed to fetch starter pack', {safeMessage: e}) // don't tell the user, just get them through onboarding. @@ -109,19 +107,15 @@ export function StepFinished() { appviewClient, [BSKY_APP_ACCOUNT_DID, ...(listItems?.map(i => i.subject.did) ?? [])], starterPack - ? // the starter pack view is still legacy-typed - { - uri: starterPack.uri as AtUriString, - cid: starterPack.cid, - } + ? {uri: starterPack.uri, cid: starterPack.cid} : undefined, ), (async () => { // 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 - const feedsToSave: AppBskyActorDefs.SavedFeed[] = [ + const feedsToSave: app.bsky.actor.defs.SavedFeed[] = [ { ...DISCOVER_SAVED_FEED, id: TID.nextStr(), @@ -140,7 +134,7 @@ export function StepFinished() { if (starterPack && starterPack.feeds?.length) { feedsToSave.push( ...starterPack.feeds.map(f => ({ - type: 'feed', + type: 'feed' as const, value: f.uri, pinned: true, id: TID.nextStr(), @@ -148,7 +142,7 @@ export function StepFinished() { ) } - await agent.overwriteSavedFeeds(feedsToSave) + await pdsClient.call(overwriteSavedFeeds, feedsToSave) })(), (async () => { const {imageUri, imageMime} = profileStepResults @@ -157,13 +151,13 @@ export function StepFinished() { ? uploadBlob(pdsClient, imageUri, imageMime) : undefined - await agent.upsertProfile(async existing => { - let next: Un$Typed = existing ?? {} + await pdsClient.call(upsertProfile, async existing => { + let next: Un$Typed = existing ?? {} if (blobPromise) { const res = await blobPromise if (res.blob) { - next.avatar = toLegacyBlobRef(res.blob) + next.avatar = res.blob } } @@ -177,7 +171,7 @@ export function StepFinished() { next.displayName = '' if (!next.createdAt) { - next.createdAt = new Date().toISOString() + next.createdAt = toDatetimeString(new Date()) } return next }) @@ -204,7 +198,7 @@ export function StepFinished() { queryKey: preferencesQueryKey, }), queryClient.invalidateQueries({ - queryKey: profileRQKey(agent.session?.did ?? ''), + queryKey: profileRQKey(pdsClient.did ?? ''), }), ]).catch(e => { logger.error(e) @@ -221,10 +215,7 @@ export function StepFinished() { usedStarterPack: Boolean(starterPack), starterPackName: starterPack && - bsky.dangerousIsType( - starterPack.record, - AppBskyGraphStarterpack.isRecord, - ) + bsky.isType(app.bsky.graph.starterpack, starterPack.record) ? starterPack.record.name : undefined, starterPackCreator: starterPack?.creator.did, @@ -242,9 +233,8 @@ export function StepFinished() { }, [ ax, queryClient, - agent, - appviewClient, pdsClient, + appviewClient, dispatch, onboardDispatch, activeStarterPack, diff --git a/src/screens/Settings/InterestsSettings.tsx b/src/screens/Settings/InterestsSettings.tsx index ced7148669..351ee60ffc 100644 --- a/src/screens/Settings/InterestsSettings.tsx +++ b/src/screens/Settings/InterestsSettings.tsx @@ -1,5 +1,6 @@ import {useMemo, useState} from 'react' import {type TextStyle, View, type ViewStyle} from 'react-native' +import {setInterestsPref} from '@bsky.app/sdk' import {msg} from '@lingui/core/macro' import {useLingui} from '@lingui/react' import {Trans} from '@lingui/react/macro' @@ -23,7 +24,7 @@ import {createGetSuggestedUsersForDiscoverQueryKey} from '#/state/queries/trendi import {createGetSuggestedUsersForExploreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForExploreQuery' import {createGetSuggestedUsersForSeeMoreQueryKey} from '#/state/queries/trending/useGetSuggestedUsersForSeeMoreQuery' 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 {Admonition} from '#/components/Admonition' import {Divider} from '#/components/Divider' @@ -88,7 +89,7 @@ function Inner({ setIsSaving: (isSaving: boolean) => void }) { const {_} = useLingui() - const agent = useAgent() + const pdsClient = usePdsClient() const qc = useQueryClient() const interestsDisplayNames = useInterestsDisplayNames() const preselectedInterests = useMemo( @@ -110,7 +111,7 @@ function Inner({ setIsSaving(true) try { - await agent.setInterestsPref({tags: interests}) + await pdsClient.call(setInterestsPref, {tags: interests}) qc.setQueriesData( {queryKey: preferencesQueryKey}, (old?: UsePreferencesQueryResponse) => { @@ -157,7 +158,7 @@ function Inner({ setIsSaving(false) } }, 1500) - }, [_, agent, setIsSaving, qc, preselectedInterests]) + }, [_, pdsClient, setIsSaving, qc, preselectedInterests]) const onChangeInterests = async (interests: string[]) => { setInterests(interests) diff --git a/src/screens/Settings/Settings.tsx b/src/screens/Settings/Settings.tsx index 37d0e9b5fa..0d5b2bcc73 100644 --- a/src/screens/Settings/Settings.tsx +++ b/src/screens/Settings/Settings.tsx @@ -2,6 +2,7 @@ import {useState} from 'react' import {Alert, LayoutAnimation, Linking, Pressable, View} from 'react-native' import {useReducedMotion} from 'react-native-reanimated' import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' +import {removeNuxs} from '@bsky.app/sdk' import {Trans, useLingui} from '@lingui/react/macro' import {useNavigation} from '@react-navigation/native' 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 {useDeleteActorDeclaration} from '#/state/queries/messages/actor-declaration' 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 {useOnboardingDispatch} from '#/state/shell' import {useLoggedOutViewControls} from '#/state/shell/logged-out' @@ -385,7 +386,7 @@ function ProfilePreview({ function DevOptions() { const {t: l} = useLingui() - const agent = useAgent() + const pdsClient = usePdsClient() const [override, setOverride] = useStorage(device, [ 'policyUpdateDebugOverride', ]) @@ -560,7 +561,7 @@ function DevOptions() {