Get more rigorous about getAgent() consistency (#2026)

* Get more rigorous about getAgent() consistency

* Update the feed wrapper API to use getAgent() directly
zio/stable
Paul Frazee 2023-11-29 10:10:04 -08:00 committed by GitHub
parent 9fb2c29c67
commit 6fe2b52f68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 70 additions and 103 deletions

View File

@ -1,18 +1,15 @@
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( constructor(public params: GetAuthorFeed.QueryParams) {}
public agent: BskyAgent,
public params: GetAuthorFeed.QueryParams,
) {}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getAuthorFeed({ const res = await getAgent().getAuthorFeed({
...this.params, ...this.params,
limit: 1, limit: 1,
}) })
@ -26,7 +23,7 @@ export class AuthorFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): Promise<FeedAPIResponse> {
const res = await this.agent.getAuthorFeed({ const res = await getAgent().getAuthorFeed({
...this.params, ...this.params,
cursor, cursor,
limit, limit,

View File

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

View File

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

View File

@ -1,18 +1,15 @@
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( constructor(public params: GetActorLikes.QueryParams) {}
public agent: BskyAgent,
public params: GetActorLikes.QueryParams,
) {}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getActorLikes({ const res = await getAgent().getActorLikes({
...this.params, ...this.params,
limit: 1, limit: 1,
}) })
@ -26,7 +23,7 @@ export class LikesFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): Promise<FeedAPIResponse> {
const res = await this.agent.getActorLikes({ const res = await getAgent().getActorLikes({
...this.params, ...this.params,
cursor, cursor,
limit, limit,

View File

@ -1,18 +1,15 @@
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( constructor(public params: GetListFeed.QueryParams) {}
public agent: BskyAgent,
public params: GetListFeed.QueryParams,
) {}
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.app.bsky.feed.getListFeed({ const res = await getAgent().app.bsky.feed.getListFeed({
...this.params, ...this.params,
limit: 1, limit: 1,
}) })
@ -26,7 +23,7 @@ export class ListFeedAPI implements FeedAPI {
cursor: string | undefined cursor: string | undefined
limit: number limit: number
}): Promise<FeedAPIResponse> { }): Promise<FeedAPIResponse> {
const res = await this.agent.app.bsky.feed.getListFeed({ const res = await getAgent().app.bsky.feed.getListFeed({
...this.params, ...this.params,
cursor, cursor,
limit, limit,

View File

@ -1,4 +1,4 @@
import {AppBskyFeedDefs, AppBskyFeedGetTimeline, BskyAgent} from '@atproto/api' import {AppBskyFeedDefs, AppBskyFeedGetTimeline} from '@atproto/api'
import shuffle from 'lodash.shuffle' import shuffle from 'lodash.shuffle'
import {timeout} from 'lib/async/timeout' import {timeout} from 'lib/async/timeout'
import {bundleAsync} from 'lib/async/bundle' import {bundleAsync} from 'lib/async/bundle'
@ -7,6 +7,7 @@ import {FeedTuner} from '../feed-manip'
import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types' import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
import {FeedParams} from '#/state/queries/post-feed' import {FeedParams} from '#/state/queries/post-feed'
import {FeedTunerFn} from '../feed-manip' import {FeedTunerFn} from '../feed-manip'
import {getAgent} from '#/state/session'
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
@ -18,16 +19,12 @@ export class MergeFeedAPI implements FeedAPI {
itemCursor = 0 itemCursor = 0
sampleCursor = 0 sampleCursor = 0
constructor( constructor(public params: FeedParams, public feedTuners: FeedTunerFn[]) {
public agent: BskyAgent, this.following = new MergeFeedSource_Following(this.feedTuners)
public params: FeedParams,
public feedTuners: FeedTunerFn[],
) {
this.following = new MergeFeedSource_Following(this.agent, this.feedTuners)
} }
reset() { reset() {
this.following = new MergeFeedSource_Following(this.agent, this.feedTuners) this.following = new MergeFeedSource_Following(this.feedTuners)
this.customFeeds = [] // just empty the array, they will be captured in _fetchNext() this.customFeeds = [] // just empty the array, they will be captured in _fetchNext()
this.feedCursor = 0 this.feedCursor = 0
this.itemCursor = 0 this.itemCursor = 0
@ -35,8 +32,7 @@ export class MergeFeedAPI implements FeedAPI {
if (this.params.mergeFeedEnabled && this.params.mergeFeedSources) { if (this.params.mergeFeedEnabled && this.params.mergeFeedSources) {
this.customFeeds = shuffle( this.customFeeds = shuffle(
this.params.mergeFeedSources.map( this.params.mergeFeedSources.map(
feedUri => feedUri => new MergeFeedSource_Custom(feedUri, this.feedTuners),
new MergeFeedSource_Custom(this.agent, feedUri, this.feedTuners),
), ),
) )
} else { } else {
@ -45,7 +41,7 @@ export class MergeFeedAPI implements FeedAPI {
} }
async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> { async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
const res = await this.agent.getTimeline({ const res = await getAgent().getTimeline({
limit: 1, limit: 1,
}) })
return res.data.feed[0] return res.data.feed[0]
@ -137,7 +133,7 @@ class MergeFeedSource {
queue: AppBskyFeedDefs.FeedViewPost[] = [] queue: AppBskyFeedDefs.FeedViewPost[] = []
hasMore = true hasMore = true
constructor(public agent: BskyAgent, public feedTuners: FeedTunerFn[]) {} constructor(public feedTuners: FeedTunerFn[]) {}
get numReady() { get numReady() {
return this.queue.length return this.queue.length
@ -199,7 +195,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 this.agent.getTimeline({cursor, limit}) const res = await 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, this.feedTuners, { const slices = this.tuner.tune(res.data.feed, this.feedTuners, {
dryRun: false, dryRun: false,
@ -213,20 +209,16 @@ class MergeFeedSource_Following extends MergeFeedSource {
class MergeFeedSource_Custom extends MergeFeedSource { class MergeFeedSource_Custom extends MergeFeedSource {
minDate: Date minDate: Date
constructor( constructor(public feedUri: string, public feedTuners: FeedTunerFn[]) {
public agent: BskyAgent, super(feedTuners)
public feedUri: string,
public feedTuners: FeedTunerFn[],
) {
super(agent, feedTuners)
this.sourceInfo = { this.sourceInfo = {
$type: 'reasonFeedSource', $type: 'reasonFeedSource',
displayName: feedUri.split('/').pop() || '', displayName: feedUri.split('/').pop() || '',
uri: feedUriToHref(feedUri), uri: feedUriToHref(feedUri),
} }
this.minDate = new Date(Date.now() - POST_AGE_CUTOFF) this.minDate = new Date(Date.now() - POST_AGE_CUTOFF)
this.agent.app.bsky.feed getAgent()
.getFeedGenerator({ .app.bsky.feed.getFeedGenerator({
feed: feedUri, feed: feedUri,
}) })
.then( .then(
@ -244,7 +236,7 @@ class MergeFeedSource_Custom extends MergeFeedSource {
limit: number, limit: number,
): Promise<AppBskyFeedGetTimeline.Response> { ): Promise<AppBskyFeedGetTimeline.Response> {
try { try {
const res = await this.agent.app.bsky.feed.getFeed({ const res = await getAgent().app.bsky.feed.getFeed({
cursor, cursor,
limit, limit,
feed: this.feedUri, feed: this.feedUri,

View File

@ -3,7 +3,6 @@ import {
AppBskyGraphGetList, AppBskyGraphGetList,
AppBskyGraphList, AppBskyGraphList,
AppBskyGraphDefs, AppBskyGraphDefs,
BskyAgent,
} from '@atproto/api' } from '@atproto/api'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query' import {useQuery, useMutation, useQueryClient} from '@tanstack/react-query'
@ -75,13 +74,9 @@ export function useListCreateMutation() {
) )
// wait for the appview to update // wait for the appview to update
await whenAppViewReady( await whenAppViewReady(res.uri, (v: AppBskyGraphGetList.Response) => {
getAgent(), return typeof v?.data?.list.uri === 'string'
res.uri, })
(v: AppBskyGraphGetList.Response) => {
return typeof v?.data?.list.uri === 'string'
},
)
return res return res
}, },
onSuccess() { onSuccess() {
@ -142,16 +137,12 @@ export function useListMetadataMutation() {
).data ).data
// wait for the appview to update // wait for the appview to update
await whenAppViewReady( await whenAppViewReady(res.uri, (v: AppBskyGraphGetList.Response) => {
getAgent(), const list = v.data.list
res.uri, return (
(v: AppBskyGraphGetList.Response) => { list.name === record.name && list.description === record.description
const list = v.data.list )
return ( })
list.name === record.name && list.description === record.description
)
},
)
return res return res
}, },
onSuccess(data, variables) { onSuccess(data, variables) {
@ -216,13 +207,9 @@ export function useListDeleteMutation() {
} }
// wait for the appview to update // wait for the appview to update
await whenAppViewReady( await whenAppViewReady(uri, (v: AppBskyGraphGetList.Response) => {
getAgent(), return !v?.success
uri, })
(v: AppBskyGraphGetList.Response) => {
return !v?.success
},
)
}, },
onSuccess() { onSuccess() {
invalidateMyLists(queryClient) invalidateMyLists(queryClient)
@ -271,7 +258,6 @@ export function useListBlockMutation() {
} }
async function whenAppViewReady( async function whenAppViewReady(
agent: BskyAgent,
uri: string, uri: string,
fn: (res: AppBskyGraphGetList.Response) => boolean, fn: (res: AppBskyGraphGetList.Response) => boolean,
) { ) {
@ -280,7 +266,7 @@ async function whenAppViewReady(
1e3, // 1s delay between tries 1e3, // 1s delay between tries
fn, fn,
() => () =>
agent.app.bsky.graph.getList({ getAgent().app.bsky.graph.getList({
list: uri, list: uri,
limit: 1, limit: 1,
}), }),

View File

@ -4,7 +4,6 @@ import {
AppBskyFeedRepost, AppBskyFeedRepost,
AppBskyFeedLike, AppBskyFeedLike,
AppBskyNotificationListNotifications, AppBskyNotificationListNotifications,
BskyAgent,
} from '@atproto/api' } from '@atproto/api'
import chunk from 'lodash.chunk' import chunk from 'lodash.chunk'
import { import {
@ -84,7 +83,7 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
// we fetch subjects of notifications (usually posts) now instead of lazily // we fetch subjects of notifications (usually posts) now instead of lazily
// in the UI to avoid relayouts // in the UI to avoid relayouts
const subjects = await fetchSubjects(getAgent(), notifsGrouped) const subjects = await fetchSubjects(notifsGrouped)
for (const notif of notifsGrouped) { for (const notif of notifsGrouped) {
if (notif.subjectUri) { if (notif.subjectUri) {
notif.subject = subjects.get(notif.subjectUri) notif.subject = subjects.get(notif.subjectUri)
@ -173,7 +172,6 @@ function groupNotifications(
} }
async function fetchSubjects( async function fetchSubjects(
agent: BskyAgent,
groupedNotifs: FeedNotification[], groupedNotifs: FeedNotification[],
): Promise<Map<string, AppBskyFeedDefs.PostView>> { ): Promise<Map<string, AppBskyFeedDefs.PostView>> {
const uris = new Set<string>() const uris = new Set<string>()
@ -185,7 +183,9 @@ async function fetchSubjects(
const uriChunks = chunk(Array.from(uris), 25) const uriChunks = chunk(Array.from(uris), 25)
const postsChunks = await Promise.all( const postsChunks = await Promise.all(
uriChunks.map(uris => uriChunks.map(uris =>
agent.app.bsky.feed.getPosts({uris}).then(res => res.data.posts), getAgent()
.app.bsky.feed.getPosts({uris})
.then(res => res.data.posts),
), ),
) )
const map = new Map<string, AppBskyFeedDefs.PostView>() const map = new Map<string, AppBskyFeedDefs.PostView>()

View File

@ -70,12 +70,10 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
}, },
async checkUnread() { async checkUnread() {
const agent = getAgent() if (!getAgent().session) return
if (!agent.session) return
// count // count
const res = await agent.listNotifications({limit: 40}) const res = await getAgent().listNotifications({limit: 40})
const filtered = res.data.notifications.filter( const filtered = res.data.notifications.filter(
notif => !notif.isRead && !shouldFilterNotif(notif, moderationOpts), notif => !notif.isRead && !shouldFilterNotif(notif, moderationOpts),
) )

View File

@ -7,7 +7,6 @@ import {
QueryClient, QueryClient,
useQueryClient, useQueryClient,
} from '@tanstack/react-query' } from '@tanstack/react-query'
import {getAgent} from '../session'
import {useFeedTuners} from '../preferences/feed-tuners' import {useFeedTuners} from '../preferences/feed-tuners'
import {FeedTuner, NoopFeedTuner} from 'lib/api/feed-manip' import {FeedTuner, NoopFeedTuner} from 'lib/api/feed-manip'
import {FeedAPI, ReasonFeedSource} from 'lib/api/feed/types' import {FeedAPI, ReasonFeedSource} from 'lib/api/feed/types'
@ -77,30 +76,29 @@ export function usePostFeedQuery(
const feedTuners = useFeedTuners(feedDesc) const feedTuners = useFeedTuners(feedDesc)
const enabled = opts?.enabled !== false const enabled = opts?.enabled !== false
const moderationOpts = useModerationOpts() const moderationOpts = useModerationOpts()
const agent = getAgent()
const api: FeedAPI = useMemo(() => { const api: FeedAPI = useMemo(() => {
if (feedDesc === 'home') { if (feedDesc === 'home') {
return new MergeFeedAPI(agent, params || {}, feedTuners) return new MergeFeedAPI(params || {}, feedTuners)
} else if (feedDesc === 'following') { } else if (feedDesc === 'following') {
return new FollowingFeedAPI(agent) return new FollowingFeedAPI()
} else if (feedDesc.startsWith('author')) { } else if (feedDesc.startsWith('author')) {
const [_, actor, filter] = feedDesc.split('|') const [_, actor, filter] = feedDesc.split('|')
return new AuthorFeedAPI(agent, {actor, filter}) return new AuthorFeedAPI({actor, filter})
} else if (feedDesc.startsWith('likes')) { } else if (feedDesc.startsWith('likes')) {
const [_, actor] = feedDesc.split('|') const [_, actor] = feedDesc.split('|')
return new LikesFeedAPI(agent, {actor}) return new LikesFeedAPI({actor})
} else if (feedDesc.startsWith('feedgen')) { } else if (feedDesc.startsWith('feedgen')) {
const [_, feed] = feedDesc.split('|') const [_, feed] = feedDesc.split('|')
return new CustomFeedAPI(agent, {feed}) return new CustomFeedAPI({feed})
} else if (feedDesc.startsWith('list')) { } else if (feedDesc.startsWith('list')) {
const [_, list] = feedDesc.split('|') const [_, list] = feedDesc.split('|')
return new ListFeedAPI(agent, {list}) return new ListFeedAPI({list})
} else { } else {
// shouldnt happen // shouldnt happen
return new FollowingFeedAPI(agent) return new FollowingFeedAPI()
} }
}, [feedDesc, params, feedTuners, agent]) }, [feedDesc, params, feedTuners])
const disableTuner = !!params?.disableTuner const disableTuner = !!params?.disableTuner
const tuner = useMemo( const tuner = useMemo(

View File

@ -4,7 +4,6 @@ import {
AppBskyActorDefs, AppBskyActorDefs,
AppBskyActorProfile, AppBskyActorProfile,
AppBskyActorGetProfile, AppBskyActorGetProfile,
BskyAgent,
} from '@atproto/api' } from '@atproto/api'
import {useQuery, useQueryClient, useMutation} from '@tanstack/react-query' import {useQuery, useQueryClient, useMutation} from '@tanstack/react-query'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
@ -68,7 +67,7 @@ export function useProfileUpdateMutation() {
} }
return existing return existing
}) })
await whenAppViewReady(getAgent(), profile.did, res => { await whenAppViewReady(profile.did, res => {
if (typeof newUserAvatar !== 'undefined') { if (typeof newUserAvatar !== 'undefined') {
if (newUserAvatar === null && res.data.avatar) { if (newUserAvatar === null && res.data.avatar) {
// url hasnt cleared yet // url hasnt cleared yet
@ -464,7 +463,6 @@ function useProfileUnblockMutation() {
} }
async function whenAppViewReady( async function whenAppViewReady(
agent: BskyAgent,
actor: string, actor: string,
fn: (res: AppBskyActorGetProfile.Response) => boolean, fn: (res: AppBskyActorGetProfile.Response) => boolean,
) { ) {
@ -472,6 +470,6 @@ 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}), () => getAgent().app.bsky.actor.getProfile({actor}),
) )
} }

View File

@ -13,6 +13,12 @@ import {useCloseAllActiveElements} from '#/state/util'
let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT let __globalAgent: BskyAgent = PUBLIC_BSKY_AGENT
/**
* NOTE
* Never hold on to the object returned by this function.
* Call `getAgent()` at the time of invocation to ensure
* that you never have a stale agent.
*/
export function getAgent() { export function getAgent() {
return __globalAgent return __globalAgent
} }