From 6e59eeb9a6a2447463224a69fce753c253a16ca0 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 3 Aug 2026 17:57:58 +0300 Subject: [PATCH] migrate the feed source and feed feedback calls to the appview client Co-Authored-By: Claude Fable 5 --- src/state/feed-feedback.tsx | 35 ++++++---- src/state/queries/feed.ts | 124 ++++++++++++++++++++---------------- 2 files changed, 90 insertions(+), 69 deletions(-) diff --git a/src/state/feed-feedback.tsx b/src/state/feed-feedback.tsx index 15c12e732d..dbd10b3c9d 100644 --- a/src/state/feed-feedback.tsx +++ b/src/state/feed-feedback.tsx @@ -8,6 +8,7 @@ import { } from 'react' import {AppState, type AppStateStatus} from 'react-native' import {type AppBskyFeedDefs} from '@atproto/api' +import {type AtUriString, type DidString} from '@atproto/syntax' import throttle from 'lodash.throttle' import {PROD_FEEDS, STAGING_FEEDS} from '#/lib/constants' @@ -22,7 +23,8 @@ import { } from '#/state/queries/post-feed' import {getItemsForFeedback} from '#/view/com/posts/PostFeed' import {useAnalytics} from '#/analytics' -import {useAgent} from './session' +import {app} from '#/lexicons' +import {useAppviewClient} from './session' export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS] @@ -65,7 +67,7 @@ export function useFeedFeedback( ) { const ax = useAnalytics() const logger = ax.logger.useChild(ax.logger.Context.FeedFeedback) - const agent = useAgent() + const client = useAppviewClient() const feed = !!feedSourceInfo && isFeedSourceFeedInfo(feedSourceInfo) @@ -150,16 +152,19 @@ export function useFeedFeedback( return } - // Send to the feed - agent.app.bsky.feed - .sendInteractions( - {interactions: interactionsToSend, feed: feed?.uri}, + /* + * Send to the feed. Interactions go to the feed generator rather than the + * appview, which the agent did by setting `atproto-proxy` by hand; the + * client's per-call `service` option writes that same header. + */ + client + .call( + app.bsky.feed.sendInteractions, { - encoding: 'application/json', - headers: { - 'atproto-proxy': `${proxyDid}#bsky_fg`, - }, + interactions: interactionsToSend, + feed: feed?.uri as AtUriString | undefined, }, + {service: `${proxyDid as DidString}#bsky_fg`}, ) .catch(() => {}) // ignore upstream errors @@ -172,7 +177,7 @@ export function useFeedFeedback( ) throttledFlushAggregatedStats() logger.debug('flushed') - }, [agent, throttledFlushAggregatedStats, proxyDid, enabled, feed]) + }, [client, throttledFlushAggregatedStats, proxyDid, enabled, feed]) const sendToFeed = useMemo( () => @@ -283,9 +288,13 @@ function toString(interaction: AppBskyFeedDefs.Interaction): string { }|${interaction.reqId || ''}` } -function toInteraction(str: string): AppBskyFeedDefs.Interaction { +function toInteraction(str: string): app.bsky.feed.defs.Interaction { const [item, event, feedContext, reqId] = str.split('|') - return {item, event, feedContext, reqId} + /* + * The fields come from splitting an internally-built key, so neither the + * at-uri nor the event token is narrowed by the compiler here. + */ + return {item, event, feedContext, reqId} as app.bsky.feed.defs.Interaction } type AggregatedStats = { diff --git a/src/state/queries/feed.ts b/src/state/queries/feed.ts index c3ac1acd53..9a9a8c09b9 100644 --- a/src/state/queries/feed.ts +++ b/src/state/queries/feed.ts @@ -3,11 +3,11 @@ import { type AppBskyActorDefs, type AppBskyFeedDefs, type AppBskyGraphDefs, - type AppBskyUnspeccedGetPopularFeedGenerators, AtUri, moderateFeedGenerator, RichText, } from '@atproto/api' +import {type AtUriString} from '@atproto/syntax' import {t} from '@lingui/core/macro' import { type InfiniteData, @@ -26,7 +26,8 @@ import {GCTIME, STALE} from '#/state/queries' import {RQKEY as listQueryKey} from '#/state/queries/list' import {usePreferencesQuery} from '#/state/queries/preferences' import {createQueryKey} from '#/state/queries/util' -import {useAgent, useSession} from '#/state/session' +import {useAppviewClient, useSession} from '#/state/session' +import {app} from '#/lexicons' import {router} from '#/routes' import {useModerationOpts} from '../preferences/moderation-opts' import {type FeedDescriptor} from './post-feed' @@ -184,7 +185,7 @@ export function getAvatarTypeFromUri(uri: string) { export function useFeedSourceInfoQuery({uri}: {uri: string}) { const type = getFeedTypeFromUri(uri) - const agent = useAgent() + const client = useAppviewClient() return useQuery({ staleTime: STALE.INFINITY, @@ -193,14 +194,16 @@ export function useFeedSourceInfoQuery({uri}: {uri: string}) { let view: FeedSourceInfo if (type === 'feed') { - const res = await agent.app.bsky.feed.getFeedGenerator({feed: uri}) - view = hydrateFeedGenerator(res.data.view) + const data = await client.call(app.bsky.feed.getFeedGenerator, { + feed: uri as AtUriString, + }) + view = hydrateFeedGenerator(data.view) } else { - const res = await agent.app.bsky.graph.getList({ - list: uri, + const data = await client.call(app.bsky.graph.getList, { + list: uri as AtUriString, limit: 1, }) - view = hydrateList(res.data.list) + view = hydrateList(data.list) } return view @@ -234,7 +237,7 @@ export function createGetPopularFeedsQueryKey( export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { const {hasSession} = useSession() - const agent = useAgent() + const client = useAppviewClient() const limit = options?.limit || 10 const {data: preferences} = usePreferencesQuery() const queryClient = useQueryClient() @@ -255,24 +258,27 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { enabled: Boolean(moderationOpts) && options?.enabled !== false, queryKey: createGetPopularFeedsQueryKey(options), queryFn: async ({pageParam}) => { - const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({ - limit, - cursor: pageParam, - }) + const data = await client.call( + app.bsky.unspecced.getPopularFeedGenerators, + { + limit, + cursor: pageParam, + }, + ) // precache feeds - for (const feed of res.data.feeds) { + for (const feed of data.feeds) { const hydratedFeed = hydrateFeedGenerator(feed) precacheFeed(queryClient, hydratedFeed) } - return res.data + return data }, initialPageParam: undefined as string | undefined, getNextPageParam: lastPage => lastPage.cursor, select: useCallback( ( - data: InfiniteData, + data: InfiniteData, ) => { const { savedFeeds, @@ -336,24 +342,27 @@ export function useGetPopularFeedsQuery(options?: GetPopularFeedsOptions) { } export function useSearchPopularFeedsMutation() { - const agent = useAgent() + const client = useAppviewClient() const moderationOpts = useModerationOpts() return useMutation({ mutationFn: async (query: string) => { - const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({ - limit: 10, - query: query, - }) + const data = await client.call( + app.bsky.unspecced.getPopularFeedGenerators, + { + limit: 10, + query: query, + }, + ) if (moderationOpts) { - return res.data.feeds.filter(feed => { + return data.feeds.filter(feed => { const decision = moderateFeedGenerator(feed, moderationOpts) return !decision.ui('contentMedia').blur }) } - return res.data.feeds + return data.feeds }, }) } @@ -371,7 +380,7 @@ export function usePopularFeedsSearch({ query: string enabled?: boolean }) { - const agent = useAgent() + const client = useAppviewClient() const moderationOpts = useModerationOpts() const enabledInner = enabled ?? Boolean(moderationOpts) @@ -379,12 +388,15 @@ export function usePopularFeedsSearch({ enabled: enabledInner, queryKey: createPopularFeedsSearchQueryKey(query), queryFn: async () => { - const res = await agent.app.bsky.unspecced.getPopularFeedGenerators({ - limit: 15, - query: query, - }) + const data = await client.call( + app.bsky.unspecced.getPopularFeedGenerators, + { + limit: 15, + query: query, + }, + ) - return res.data.feeds + return data.feeds }, placeholderData: keepPreviousData, select(data) { @@ -444,7 +456,7 @@ const createPinnedFeedInfosQueryKey = ( export function usePinnedFeedsInfos() { const {hasSession} = useSession() - const agent = useAgent() + const client = useAppviewClient() const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery() const pinnedItems = preferences?.savedFeeds.filter(feed => feed.pinned) ?? [] @@ -467,13 +479,13 @@ export function usePinnedFeedsInfos() { const pinnedFeeds = pinnedItems.filter(feed => feed.type === 'feed') let feedsPromise = Promise.resolve() if (pinnedFeeds.length > 0) { - feedsPromise = agent.app.bsky.feed - .getFeedGenerators({ - feeds: pinnedFeeds.map(f => f.value), + feedsPromise = client + .call(app.bsky.feed.getFeedGenerators, { + feeds: pinnedFeeds.map(f => f.value as AtUriString), }) - .then(res => { - for (let i = 0; i < res.data.feeds.length; i++) { - const feedView = res.data.feeds[i] + .then(data => { + for (let i = 0; i < data.feeds.length; i++) { + const feedView = data.feeds[i] resolved.set(feedView.uri, hydrateFeedGenerator(feedView)) } }) @@ -482,13 +494,13 @@ export function usePinnedFeedsInfos() { // Get all lists. This currently has to be done individually. const pinnedLists = pinnedItems.filter(feed => feed.type === 'list') const listsPromises = pinnedLists.map(list => - agent.app.bsky.graph - .getList({ - list: list.value, + client + .call(app.bsky.graph.getList, { + list: list.value as AtUriString, limit: 1, }) - .then(res => { - const listView = res.data.list + .then(data => { + const listView = data.list resolved.set(listView.uri, hydrateList(listView)) }), ) @@ -551,7 +563,7 @@ export type SavedFeedItem = } export function useSavedFeeds() { - const agent = useAgent() + const client = useAppviewClient() const {data: preferences, isLoading: isLoadingPrefs} = usePreferencesQuery() const savedItems = preferences?.savedFeeds ?? [] const queryClient = useQueryClient() @@ -582,25 +594,25 @@ export function useSavedFeeds() { let feedsPromise = Promise.resolve() if (savedFeeds.length > 0) { - feedsPromise = agent.app.bsky.feed - .getFeedGenerators({ - feeds: savedFeeds.map(f => f.value), + feedsPromise = client + .call(app.bsky.feed.getFeedGenerators, { + feeds: savedFeeds.map(f => f.value as AtUriString), }) - .then(res => { - res.data.feeds.forEach(f => { + .then(data => { + data.feeds.forEach(f => { resolvedFeeds.set(f.uri, f) }) }) } const listsPromises = savedLists.map(list => - agent.app.bsky.graph - .getList({ - list: list.value, + client + .call(app.bsky.graph.getList, { + list: list.value as AtUriString, limit: 1, }) - .then(res => { - const listView = res.data.list + .then(data => { + const listView = data.list resolvedLists.set(listView.uri, listView) }), ) @@ -656,7 +668,7 @@ export function useSavedFeeds() { const feedInfoQueryKeyRoot = 'feedInfo' export function useFeedInfo(feedUri: string | undefined) { - const agent = useAgent() + const client = useAppviewClient() return useQuery({ staleTime: STALE.INFINITY, @@ -666,11 +678,11 @@ export function useFeedInfo(feedUri: string | undefined) { return null } - const res = await agent.app.bsky.feed.getFeedGenerator({ - feed: feedUri, + const data = await client.call(app.bsky.feed.getFeedGenerator, { + feed: feedUri as AtUriString, }) - const feedSourceInfo = hydrateFeedGenerator(res.data.view) + const feedSourceInfo = hydrateFeedGenerator(data.view) return feedSourceInfo }, })