diff options
Diffstat (limited to 'src/state')
-rw-r--r-- | src/state/models/cache/my-follows.ts | 83 | ||||
-rw-r--r-- | src/state/models/content/profile.ts | 8 | ||||
-rw-r--r-- | src/state/models/discovery/foafs.ts | 25 | ||||
-rw-r--r-- | src/state/models/discovery/suggested-actors.ts | 5 | ||||
-rw-r--r-- | src/state/models/feeds/posts.ts | 4 | ||||
-rw-r--r-- | src/state/models/lists/likes.ts | 3 | ||||
-rw-r--r-- | src/state/models/me.ts | 3 | ||||
-rw-r--r-- | src/state/models/root-store.ts | 1 | ||||
-rw-r--r-- | src/state/models/ui/search.ts | 3 |
9 files changed, 70 insertions, 65 deletions
diff --git a/src/state/models/cache/my-follows.ts b/src/state/models/cache/my-follows.ts index eaab829bc..10f88c4a9 100644 --- a/src/state/models/cache/my-follows.ts +++ b/src/state/models/cache/my-follows.ts @@ -1,13 +1,15 @@ -import {makeAutoObservable, runInAction} from 'mobx' -import {FollowRecord, AppBskyActorDefs} from '@atproto/api' +import {makeAutoObservable} from 'mobx' +import {AppBskyActorDefs} from '@atproto/api' import {RootStoreModel} from '../root-store' -import {bundleAsync} from 'lib/async/bundle' -const CACHE_TTL = 1000 * 60 * 60 // hourly -type FollowsListResponse = Awaited<ReturnType<FollowRecord['list']>> -type FollowsListResponseRecord = FollowsListResponse['records'][0] type Profile = AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileView +export enum FollowState { + Following, + NotFollowing, + Unknown, +} + /** * This model is used to maintain a synced local cache of the user's * follows. It should be periodically refreshed and updated any time @@ -15,7 +17,7 @@ type Profile = AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileView */ export class MyFollowsCache { // data - followDidToRecordMap: Record<string, string> = {} + followDidToRecordMap: Record<string, string | boolean> = {} lastSync = 0 myDid?: string @@ -38,58 +40,33 @@ export class MyFollowsCache { this.myDid = undefined } - fetchIfNeeded = bundleAsync(async () => { - if ( - this.myDid !== this.rootStore.me.did || - Object.keys(this.followDidToRecordMap).length === 0 || - Date.now() - this.lastSync > CACHE_TTL - ) { - return await this.fetch() + getFollowState(did: string): FollowState { + if (typeof this.followDidToRecordMap[did] === 'undefined') { + return FollowState.Unknown } - }) - - fetch = bundleAsync(async () => { - this.rootStore.log.debug('MyFollowsModel:fetch running full fetch') - let rkeyStart - let records: FollowsListResponseRecord[] = [] - do { - const res: FollowsListResponse = - await this.rootStore.agent.app.bsky.graph.follow.list({ - repo: this.rootStore.me.did, - rkeyStart, - reverse: true, - }) - records = records.concat(res.records) - rkeyStart = res.cursor - } while (typeof rkeyStart !== 'undefined') - runInAction(() => { - this.followDidToRecordMap = {} - for (const record of records) { - this.followDidToRecordMap[record.value.subject] = record.uri - } - this.lastSync = Date.now() - this.myDid = this.rootStore.me.did - }) - }) - - isFollowing(did: string) { - return !!this.followDidToRecordMap[did] - } - - get numFollows() { - return Object.keys(this.followDidToRecordMap).length + if (typeof this.followDidToRecordMap[did] === 'string') { + return FollowState.Following + } + return FollowState.NotFollowing } - get isEmpty() { - return Object.keys(this.followDidToRecordMap).length === 0 + async fetchFollowState(did: string): Promise<FollowState> { + // TODO: can we get a more efficient method for this? getProfile fetches more data than we need -prf + const res = await this.rootStore.agent.getProfile({actor: did}) + if (res.data.viewer?.following) { + this.addFollow(did, res.data.viewer.following) + } else { + this.removeFollow(did) + } + return this.getFollowState(did) } getFollowUri(did: string): string { const v = this.followDidToRecordMap[did] - if (!v) { - throw new Error('Not a followed user') + if (typeof v === 'string') { + return v } - return v + throw new Error('Not a followed user') } addFollow(did: string, recordUri: string) { @@ -97,7 +74,7 @@ export class MyFollowsCache { } removeFollow(did: string) { - delete this.followDidToRecordMap[did] + this.followDidToRecordMap[did] = false } /** @@ -107,7 +84,7 @@ export class MyFollowsCache { if (recordUri) { this.followDidToRecordMap[did] = recordUri } else { - delete this.followDidToRecordMap[did] + this.followDidToRecordMap[did] = false } } diff --git a/src/state/models/content/profile.ts b/src/state/models/content/profile.ts index 08616bf18..8d9c71b39 100644 --- a/src/state/models/content/profile.ts +++ b/src/state/models/content/profile.ts @@ -8,6 +8,7 @@ import { import {RootStoreModel} from '../root-store' import * as apilib from 'lib/api/index' import {cleanError} from 'lib/strings/errors' +import {FollowState} from '../cache/my-follows' export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser' @@ -89,9 +90,10 @@ export class ProfileModel { } const follows = this.rootStore.me.follows - const followUri = follows.isFollowing(this.did) - ? follows.getFollowUri(this.did) - : undefined + const followUri = + (await follows.fetchFollowState(this.did)) === FollowState.Following + ? follows.getFollowUri(this.did) + : undefined // guard against this view getting out of sync with the follows cache if (followUri !== this.viewer.following) { diff --git a/src/state/models/discovery/foafs.ts b/src/state/models/discovery/foafs.ts index 27cee8503..8dac2ec2d 100644 --- a/src/state/models/discovery/foafs.ts +++ b/src/state/models/discovery/foafs.ts @@ -38,7 +38,24 @@ export class FoafsModel { fetch = bundleAsync(async () => { try { this.isLoading = true - await this.rootStore.me.follows.fetchIfNeeded() + + // fetch & hydrate up to 1000 follows + { + let cursor + for (let i = 0; i < 10; i++) { + const res = await this.rootStore.agent.getFollows({ + actor: this.rootStore.me.did, + cursor, + limit: 100, + }) + this.rootStore.me.follows.hydrateProfiles(res.data.follows) + if (!res.data.cursor) { + break + } + cursor = res.data.cursor + } + } + // grab 10 of the users followed by the user this.sources = sampleSize( Object.keys(this.rootStore.me.follows.followDidToRecordMap), @@ -66,14 +83,16 @@ export class FoafsModel { const popular: RefWithInfoAndFollowers[] = [] for (let i = 0; i < results.length; i++) { const res = results[i] + if (res.status === 'fulfilled') { + this.rootStore.me.follows.hydrateProfiles(res.value.data.follows) + } const profile = profiles.data.profiles[i] const source = this.sources[i] if (res.status === 'fulfilled' && profile) { // filter out users already followed by the user or that *is* the user res.value.data.follows = res.value.data.follows.filter(follow => { return ( - follow.did !== this.rootStore.me.did && - !this.rootStore.me.follows.isFollowing(follow.did) + follow.did !== this.rootStore.me.did && !follow.viewer?.following ) }) diff --git a/src/state/models/discovery/suggested-actors.ts b/src/state/models/discovery/suggested-actors.ts index 91c5efd02..dca81dc90 100644 --- a/src/state/models/discovery/suggested-actors.ts +++ b/src/state/models/discovery/suggested-actors.ts @@ -110,7 +110,6 @@ export class SuggestedActorsModel { if (this.hardCodedSuggestions) { return } - await this.rootStore.me.follows.fetchIfNeeded() try { // clone the array so we can mutate it const actors = [ @@ -128,9 +127,11 @@ export class SuggestedActorsModel { profiles = profiles.concat(res.data.profiles) } while (actors.length) + this.rootStore.me.follows.hydrateProfiles(profiles) + runInAction(() => { profiles = profiles.filter(profile => { - if (this.rootStore.me.follows.isFollowing(profile.did)) { + if (profile.viewer?.following) { return false } if (profile.did === this.rootStore.me.did) { diff --git a/src/state/models/feeds/posts.ts b/src/state/models/feeds/posts.ts index 0046f9781..8a726ca8b 100644 --- a/src/state/models/feeds/posts.ts +++ b/src/state/models/feeds/posts.ts @@ -543,6 +543,10 @@ export class PostsFeedModel { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor + this.rootStore.me.follows.hydrateProfiles( + res.data.feed.map(item => item.post.author), + ) + const slices = this.tuner.tune(res.data.feed, this.feedTuners) const toAppend: PostsFeedSliceModel[] = [] diff --git a/src/state/models/lists/likes.ts b/src/state/models/lists/likes.ts index e88389c56..684fd5ee9 100644 --- a/src/state/models/lists/likes.ts +++ b/src/state/models/lists/likes.ts @@ -126,6 +126,9 @@ export class LikesModel { _appendAll(res: GetLikes.Response) { this.loadMoreCursor = res.data.cursor this.hasMore = !!this.loadMoreCursor + this.rootStore.me.follows.hydrateProfiles( + res.data.likes.map(like => like.actor), + ) this.likes = this.likes.concat(res.data.likes) } } diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 26f0849c7..3adbc7c6c 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -104,9 +104,6 @@ export class MeModel { } }) this.mainFeed.clear() - await this.follows.fetch().catch(e => { - this.rootStore.log.error('Failed to load my follows', e) - }) await Promise.all([ this.mainFeed.setup().catch(e => { this.rootStore.log.error('Failed to setup main feed model', e) diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index d4fcbf74e..0d893415f 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -142,7 +142,6 @@ export class RootStoreModel { } try { await this.me.notifications.loadUnreadCount() - await this.me.follows.fetchIfNeeded() } catch (e: any) { this.log.error('Failed to fetch latest state', e) } diff --git a/src/state/models/ui/search.ts b/src/state/models/ui/search.ts index 8436b0984..330283a0b 100644 --- a/src/state/models/ui/search.ts +++ b/src/state/models/ui/search.ts @@ -43,6 +43,9 @@ export class SearchUIModel { profiles = profiles.concat(res.data.profiles) } while (profilesSearch.length) } + + this.rootStore.me.follows.hydrateProfiles(profiles) + runInAction(() => { this.profiles = profiles this.isProfilesLoading = false |