[Session] Drill `getAgent` into feed APIs (#3701)

* Update to desired post-feed usage

* Drill agent into feed apis

* Thread getAgent instead

---------

Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
zio/stable
Eric Bailey 2024-04-25 15:29:06 -05:00 committed by GitHub
parent 282ad4b17d
commit ec37696034
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 202 additions and 65 deletions

View File

@ -1,15 +1,28 @@
import { import {
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyFeedGetAuthorFeed as GetAuthorFeed, AppBskyFeedGetAuthorFeed as GetAuthorFeed,
BskyAgent,
} from '@atproto/api' } from '@atproto/api'
import {FeedAPI, FeedAPIResponse} from './types' import {FeedAPI, FeedAPIResponse} from './types'
import {getAgent} from '#/state/session'
export class AuthorFeedAPI implements FeedAPI { export class AuthorFeedAPI implements FeedAPI {
constructor(public params: GetAuthorFeed.QueryParams) {} getAgent: () => BskyAgent
params: GetAuthorFeed.QueryParams
constructor({
getAgent,
feedParams,
}: {
getAgent: () => BskyAgent
feedParams: GetAuthorFeed.QueryParams
}) {
this.getAgent = getAgent
this.params = feedParams
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await getAgent().getAuthorFeed({ const res = await this.getAgent().getAuthorFeed({
...this.params, ...this.params,
limit: 1, limit: 1,
}) })
@ -23,7 +36,7 @@ export class AuthorFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): Promise<FeedAPIResponse> {
const res = await getAgent().getAuthorFeed({ const res = await this.getAgent().getAuthorFeed({
...this.params, ...this.params,
cursor, cursor,
limit, limit,

View File

@ -2,18 +2,30 @@ import {
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyFeedGetFeed as GetCustomFeed, AppBskyFeedGetFeed as GetCustomFeed,
AtpAgent, AtpAgent,
BskyAgent,
} from '@atproto/api' } from '@atproto/api'
import {getContentLanguages} from '#/state/preferences/languages' import {getContentLanguages} from '#/state/preferences/languages'
import {getAgent} from '#/state/session'
import {FeedAPI, FeedAPIResponse} from './types' import {FeedAPI, FeedAPIResponse} from './types'
export class CustomFeedAPI implements FeedAPI { export class CustomFeedAPI implements FeedAPI {
constructor(public params: GetCustomFeed.QueryParams) {} getAgent: () => BskyAgent
params: GetCustomFeed.QueryParams
constructor({
getAgent,
feedParams,
}: {
getAgent: () => BskyAgent
feedParams: GetCustomFeed.QueryParams
}) {
this.getAgent = getAgent
this.params = feedParams
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const contentLangs = getContentLanguages().join(',') const contentLangs = getContentLanguages().join(',')
const res = await getAgent().app.bsky.feed.getFeed( const res = await this.getAgent().app.bsky.feed.getFeed(
{ {
...this.params, ...this.params,
limit: 1, limit: 1,
@ -31,15 +43,19 @@ export class CustomFeedAPI implements FeedAPI {
limit: number limit: number
}): Promise<FeedAPIResponse> { }): Promise<FeedAPIResponse> {
const contentLangs = getContentLanguages().join(',') const contentLangs = getContentLanguages().join(',')
const agent = getAgent() const agent = this.getAgent()
const res = agent.session const res = agent.session
? await getAgent().app.bsky.feed.getFeed( ? await this.getAgent().app.bsky.feed.getFeed(
{ {
...this.params, ...this.params,
cursor, cursor,
limit, limit,
}, },
{headers: {'Accept-Language': contentLangs}}, {
headers: {
'Accept-Language': contentLangs,
},
},
) )
: await loggedOutFetch({...this.params, cursor, limit}) : await loggedOutFetch({...this.params, cursor, limit})
if (res.success) { if (res.success) {

View File

@ -1,12 +1,16 @@
import {AppBskyFeedDefs} from '@atproto/api' import {AppBskyFeedDefs, BskyAgent} from '@atproto/api'
import {FeedAPI, FeedAPIResponse} from './types' import {FeedAPI, FeedAPIResponse} from './types'
import {getAgent} from '#/state/session'
export class FollowingFeedAPI implements FeedAPI { export class FollowingFeedAPI implements FeedAPI {
constructor() {} getAgent: () => BskyAgent
constructor({getAgent}: {getAgent: () => BskyAgent}) {
this.getAgent = getAgent
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await getAgent().getTimeline({ const res = await this.getAgent().getTimeline({
limit: 1, limit: 1,
}) })
return res.data.feed[0] return res.data.feed[0]
@ -19,7 +23,7 @@ export class FollowingFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): Promise<FeedAPIResponse> {
const res = await getAgent().getTimeline({ const res = await this.getAgent().getTimeline({
cursor, cursor,
limit, limit,
}) })

View File

@ -1,8 +1,9 @@
import {AppBskyFeedDefs} from '@atproto/api' import {AppBskyFeedDefs, BskyAgent} from '@atproto/api'
import {FeedAPI, FeedAPIResponse} from './types'
import {FollowingFeedAPI} from './following'
import {CustomFeedAPI} from './custom'
import {PROD_DEFAULT_FEED} from '#/lib/constants' import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {CustomFeedAPI} from './custom'
import {FollowingFeedAPI} from './following'
import {FeedAPI, FeedAPIResponse} from './types'
// HACK // HACK
// the feed API does not include any facilities for passing down // the feed API does not include any facilities for passing down
@ -26,19 +27,27 @@ export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = {
} }
export class HomeFeedAPI implements FeedAPI { export class HomeFeedAPI implements FeedAPI {
getAgent: () => BskyAgent
following: FollowingFeedAPI following: FollowingFeedAPI
discover: CustomFeedAPI discover: CustomFeedAPI
usingDiscover = false usingDiscover = false
itemCursor = 0 itemCursor = 0
constructor() { constructor({getAgent}: {getAgent: () => BskyAgent}) {
this.following = new FollowingFeedAPI() this.getAgent = getAgent
this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')}) this.following = new FollowingFeedAPI({getAgent})
this.discover = new CustomFeedAPI({
getAgent,
feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
})
} }
reset() { reset() {
this.following = new FollowingFeedAPI() this.following = new FollowingFeedAPI({getAgent: this.getAgent})
this.discover = new CustomFeedAPI({feed: PROD_DEFAULT_FEED('whats-hot')}) this.discover = new CustomFeedAPI({
getAgent: this.getAgent,
feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
})
this.usingDiscover = false this.usingDiscover = false
this.itemCursor = 0 this.itemCursor = 0
} }

View File

@ -1,15 +1,28 @@
import { import {
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyFeedGetActorLikes as GetActorLikes, AppBskyFeedGetActorLikes as GetActorLikes,
BskyAgent,
} from '@atproto/api' } from '@atproto/api'
import {FeedAPI, FeedAPIResponse} from './types' import {FeedAPI, FeedAPIResponse} from './types'
import {getAgent} from '#/state/session'
export class LikesFeedAPI implements FeedAPI { export class LikesFeedAPI implements FeedAPI {
constructor(public params: GetActorLikes.QueryParams) {} getAgent: () => BskyAgent
params: GetActorLikes.QueryParams
constructor({
getAgent,
feedParams,
}: {
getAgent: () => BskyAgent
feedParams: GetActorLikes.QueryParams
}) {
this.getAgent = getAgent
this.params = feedParams
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await getAgent().getActorLikes({ const res = await this.getAgent().getActorLikes({
...this.params, ...this.params,
limit: 1, limit: 1,
}) })
@ -23,7 +36,7 @@ export class LikesFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): Promise<FeedAPIResponse> {
const res = await getAgent().getActorLikes({ const res = await this.getAgent().getActorLikes({
...this.params, ...this.params,
cursor, cursor,
limit, limit,

View File

@ -1,15 +1,28 @@
import { import {
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyFeedGetListFeed as GetListFeed, AppBskyFeedGetListFeed as GetListFeed,
BskyAgent,
} from '@atproto/api' } from '@atproto/api'
import {FeedAPI, FeedAPIResponse} from './types' import {FeedAPI, FeedAPIResponse} from './types'
import {getAgent} from '#/state/session'
export class ListFeedAPI implements FeedAPI { export class ListFeedAPI implements FeedAPI {
constructor(public params: GetListFeed.QueryParams) {} getAgent: () => BskyAgent
params: GetListFeed.QueryParams
constructor({
getAgent,
feedParams,
}: {
getAgent: () => BskyAgent
feedParams: GetListFeed.QueryParams
}) {
this.getAgent = getAgent
this.params = feedParams
}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await getAgent().app.bsky.feed.getListFeed({ const res = await this.getAgent().app.bsky.feed.getListFeed({
...this.params, ...this.params,
limit: 1, limit: 1,
}) })
@ -23,7 +36,7 @@ export class ListFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): Promise<FeedAPIResponse> {
const res = await getAgent().app.bsky.feed.getListFeed({ const res = await this.getAgent().app.bsky.feed.getListFeed({
...this.params, ...this.params,
cursor, cursor,
limit, limit,

View File

@ -1,31 +1,51 @@
import {AppBskyFeedDefs, AppBskyFeedGetTimeline} from '@atproto/api' import {AppBskyFeedDefs, AppBskyFeedGetTimeline, BskyAgent} from '@atproto/api'
import shuffle from 'lodash.shuffle' import shuffle from 'lodash.shuffle'
import {timeout} from 'lib/async/timeout'
import {getContentLanguages} from '#/state/preferences/languages'
import {FeedParams} from '#/state/queries/post-feed'
import {bundleAsync} from 'lib/async/bundle' import {bundleAsync} from 'lib/async/bundle'
import {timeout} from 'lib/async/timeout'
import {feedUriToHref} from 'lib/strings/url-helpers' import {feedUriToHref} from 'lib/strings/url-helpers'
import {FeedTuner} from '../feed-manip' import {FeedTuner} from '../feed-manip'
import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
import {FeedParams} from '#/state/queries/post-feed'
import {FeedTunerFn} from '../feed-manip' import {FeedTunerFn} from '../feed-manip'
import {getAgent} from '#/state/session' import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
import {getContentLanguages} from '#/state/preferences/languages'
const REQUEST_WAIT_MS = 500 // 500ms const REQUEST_WAIT_MS = 500 // 500ms
const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
export class MergeFeedAPI implements FeedAPI { export class MergeFeedAPI implements FeedAPI {
getAgent: () => BskyAgent
params: FeedParams
feedTuners: FeedTunerFn[]
following: MergeFeedSource_Following following: MergeFeedSource_Following
customFeeds: MergeFeedSource_Custom[] = [] customFeeds: MergeFeedSource_Custom[] = []
feedCursor = 0 feedCursor = 0
itemCursor = 0 itemCursor = 0
sampleCursor = 0 sampleCursor = 0
constructor(public params: FeedParams, public feedTuners: FeedTunerFn[]) { constructor({
this.following = new MergeFeedSource_Following(this.feedTuners) getAgent,
feedParams,
feedTuners,
}: {
getAgent: () => BskyAgent
feedParams: FeedParams
feedTuners: FeedTunerFn[]
}) {
this.getAgent = getAgent
this.params = feedParams
this.feedTuners = feedTuners
this.following = new MergeFeedSource_Following({
getAgent: this.getAgent,
feedTuners: this.feedTuners,
})
} }
reset() { reset() {
this.following = new MergeFeedSource_Following(this.feedTuners) this.following = new MergeFeedSource_Following({
getAgent: this.getAgent,
feedTuners: this.feedTuners,
})
this.customFeeds = [] this.customFeeds = []
this.feedCursor = 0 this.feedCursor = 0
this.itemCursor = 0 this.itemCursor = 0
@ -33,7 +53,12 @@ export class MergeFeedAPI implements FeedAPI {
if (this.params.mergeFeedSources) { if (this.params.mergeFeedSources) {
this.customFeeds = shuffle( this.customFeeds = shuffle(
this.params.mergeFeedSources.map( this.params.mergeFeedSources.map(
feedUri => new MergeFeedSource_Custom(feedUri, this.feedTuners), feedUri =>
new MergeFeedSource_Custom({
getAgent: this.getAgent,
feedUri,
feedTuners: this.feedTuners,
}),
), ),
) )
} else { } else {
@ -42,7 +67,7 @@ export class MergeFeedAPI implements FeedAPI {
} }
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await getAgent().getTimeline({ const res = await this.getAgent().getTimeline({
limit: 1, limit: 1,
}) })
return res.data.feed[0] return res.data.feed[0]
@ -136,12 +161,23 @@ export class MergeFeedAPI implements FeedAPI {
} }
class MergeFeedSource { class MergeFeedSource {
getAgent: () => BskyAgent
feedTuners: FeedTunerFn[]
sourceInfo: ReasonFeedSource | undefined sourceInfo: ReasonFeedSource | undefined
cursor: string | undefined = undefined cursor: string | undefined = undefined
queue: AppBskyFeedDefs.FeedViewPost[] = [] queue: AppBskyFeedDefs.FeedViewPost[] = []
hasMore = true hasMore = true
constructor(public feedTuners: FeedTunerFn[]) {} constructor({
getAgent,
feedTuners,
}: {
getAgent: () => BskyAgent
feedTuners: FeedTunerFn[]
}) {
this.getAgent = getAgent
this.feedTuners = feedTuners
}
get numReady() { get numReady() {
return this.queue.length return this.queue.length
@ -203,7 +239,7 @@ class MergeFeedSource_Following extends MergeFeedSource {
cursor: string | undefined, cursor: string | undefined,
limit: number, limit: number,
): Promise<AppBskyFeedGetTimeline.Response> { ): Promise<AppBskyFeedGetTimeline.Response> {
const res = await getAgent().getTimeline({cursor, limit}) const res = await this.getAgent().getTimeline({cursor, limit})
// run the tuner pre-emptively to ensure better mixing // run the tuner pre-emptively to ensure better mixing
const slices = this.tuner.tune(res.data.feed, { const slices = this.tuner.tune(res.data.feed, {
dryRun: false, dryRun: false,
@ -215,10 +251,25 @@ class MergeFeedSource_Following extends MergeFeedSource {
} }
class MergeFeedSource_Custom extends MergeFeedSource { class MergeFeedSource_Custom extends MergeFeedSource {
getAgent: () => BskyAgent
minDate: Date minDate: Date
feedUri: string
constructor(public feedUri: string, public feedTuners: FeedTunerFn[]) { constructor({
super(feedTuners) getAgent,
feedUri,
feedTuners,
}: {
getAgent: () => BskyAgent
feedUri: string
feedTuners: FeedTunerFn[]
}) {
super({
getAgent,
feedTuners,
})
this.getAgent = getAgent
this.feedUri = feedUri
this.sourceInfo = { this.sourceInfo = {
$type: 'reasonFeedSource', $type: 'reasonFeedSource',
uri: feedUri, uri: feedUri,
@ -233,13 +284,17 @@ class MergeFeedSource_Custom extends MergeFeedSource {
): Promise<AppBskyFeedGetTimeline.Response> { ): Promise<AppBskyFeedGetTimeline.Response> {
try { try {
const contentLangs = getContentLanguages().join(',') const contentLangs = getContentLanguages().join(',')
const res = await getAgent().app.bsky.feed.getFeed( const res = await this.getAgent().app.bsky.feed.getFeed(
{ {
cursor, cursor,
limit, limit,
feed: this.feedUri, feed: this.feedUri,
}, },
{headers: {'Accept-Language': contentLangs}}, {
headers: {
'Accept-Language': contentLangs,
},
},
) )
// NOTE // NOTE
// some custom feeds fail to enforce the pagination limit // some custom feeds fail to enforce the pagination limit

View File

@ -135,11 +135,14 @@ export function usePostFeedQuery(
queryKey: RQKEY(feedDesc, params), queryKey: RQKEY(feedDesc, params),
async queryFn({pageParam}: {pageParam: RQPageParam}) { async queryFn({pageParam}: {pageParam: RQPageParam}) {
logger.debug('usePostFeedQuery', {feedDesc, cursor: pageParam?.cursor}) logger.debug('usePostFeedQuery', {feedDesc, cursor: pageParam?.cursor})
const {api, cursor} = pageParam const {api, cursor} = pageParam
? pageParam ? pageParam
: { : {
api: createApi(feedDesc, params || {}, feedTuners), api: createApi({
feedDesc,
feedParams: params || {},
feedTuners,
}),
cursor: undefined, cursor: undefined,
} }
@ -365,34 +368,45 @@ export async function pollLatest(page: FeedPage | undefined) {
return false return false
} }
function createApi( function createApi({
feedDesc: FeedDescriptor, feedDesc,
params: FeedParams, feedParams,
feedTuners: FeedTunerFn[], feedTuners,
) { }: {
feedDesc: FeedDescriptor
feedParams: FeedParams
feedTuners: FeedTunerFn[]
}) {
if (feedDesc === 'home') { if (feedDesc === 'home') {
if (params.mergeFeedEnabled) { if (feedParams.mergeFeedEnabled) {
return new MergeFeedAPI(params, feedTuners) return new MergeFeedAPI({
getAgent,
feedParams,
feedTuners,
})
} else { } else {
return new HomeFeedAPI() return new HomeFeedAPI({getAgent})
} }
} else if (feedDesc === 'following') { } else if (feedDesc === 'following') {
return new FollowingFeedAPI() return new FollowingFeedAPI({getAgent})
} else if (feedDesc.startsWith('author')) { } else if (feedDesc.startsWith('author')) {
const [_, actor, filter] = feedDesc.split('|') const [_, actor, filter] = feedDesc.split('|')
return new AuthorFeedAPI({actor, filter}) return new AuthorFeedAPI({getAgent, feedParams: {actor, filter}})
} else if (feedDesc.startsWith('likes')) { } else if (feedDesc.startsWith('likes')) {
const [_, actor] = feedDesc.split('|') const [_, actor] = feedDesc.split('|')
return new LikesFeedAPI({actor}) return new LikesFeedAPI({getAgent, feedParams: {actor}})
} else if (feedDesc.startsWith('feedgen')) { } else if (feedDesc.startsWith('feedgen')) {
const [_, feed] = feedDesc.split('|') const [_, feed] = feedDesc.split('|')
return new CustomFeedAPI({feed}) return new CustomFeedAPI({
getAgent,
feedParams: {feed},
})
} else if (feedDesc.startsWith('list')) { } else if (feedDesc.startsWith('list')) {
const [_, list] = feedDesc.split('|') const [_, list] = feedDesc.split('|')
return new ListFeedAPI({list}) return new ListFeedAPI({getAgent, feedParams: {list}})
} else { } else {
// shouldnt happen // shouldnt happen
return new FollowingFeedAPI() return new FollowingFeedAPI({getAgent})
} }
} }