about summary refs log tree commit diff
path: root/src/state/models/cache
diff options
context:
space:
mode:
Diffstat (limited to 'src/state/models/cache')
-rw-r--r--src/state/models/cache/my-follows.ts99
1 files changed, 67 insertions, 32 deletions
diff --git a/src/state/models/cache/my-follows.ts b/src/state/models/cache/my-follows.ts
index 10f88c4a9..14dc9895d 100644
--- a/src/state/models/cache/my-follows.ts
+++ b/src/state/models/cache/my-follows.ts
@@ -1,7 +1,14 @@
 import {makeAutoObservable} from 'mobx'
-import {AppBskyActorDefs} from '@atproto/api'
+import {
+  AppBskyActorDefs,
+  AppBskyGraphGetFollows as GetFollows,
+  moderateProfile,
+} from '@atproto/api'
 import {RootStoreModel} from '../root-store'
 
+const MAX_SYNC_PAGES = 10
+const SYNC_TTL = 60e3 * 10 // 10 minutes
+
 type Profile = AppBskyActorDefs.ProfileViewBasic | AppBskyActorDefs.ProfileView
 
 export enum FollowState {
@@ -10,6 +17,14 @@ export enum FollowState {
   Unknown,
 }
 
+export interface FollowInfo {
+  did: string
+  followRecordUri: string | undefined
+  handle: string
+  displayName: string | undefined
+  avatar: string | undefined
+}
+
 /**
  * This model is used to maintain a synced local cache of the user's
  * follows. It should be periodically refreshed and updated any time
@@ -17,9 +32,8 @@ export enum FollowState {
  */
 export class MyFollowsCache {
   // data
-  followDidToRecordMap: Record<string, string | boolean> = {}
+  byDid: Record<string, FollowInfo> = {}
   lastSync = 0
-  myDid?: string
 
   constructor(public rootStore: RootStoreModel) {
     makeAutoObservable(
@@ -35,16 +49,45 @@ export class MyFollowsCache {
   // =
 
   clear() {
-    this.followDidToRecordMap = {}
-    this.lastSync = 0
-    this.myDid = undefined
+    this.byDid = {}
+  }
+
+  /**
+   * Syncs a subset of the user's follows
+   * for performance reasons, caps out at 1000 follows
+   */
+  async syncIfNeeded() {
+    if (this.lastSync > Date.now() - SYNC_TTL) {
+      return
+    }
+
+    let cursor
+    for (let i = 0; i < MAX_SYNC_PAGES; i++) {
+      const res: GetFollows.Response = await this.rootStore.agent.getFollows({
+        actor: this.rootStore.me.did,
+        cursor,
+        limit: 100,
+      })
+      res.data.follows = res.data.follows.filter(
+        profile =>
+          !moderateProfile(profile, this.rootStore.preferences.moderationOpts)
+            .account.filter,
+      )
+      this.hydrateMany(res.data.follows)
+      if (!res.data.cursor) {
+        break
+      }
+      cursor = res.data.cursor
+    }
+
+    this.lastSync = Date.now()
   }
 
   getFollowState(did: string): FollowState {
-    if (typeof this.followDidToRecordMap[did] === 'undefined') {
+    if (typeof this.byDid[did] === 'undefined') {
       return FollowState.Unknown
     }
-    if (typeof this.followDidToRecordMap[did] === 'string') {
+    if (typeof this.byDid[did].followRecordUri === 'string') {
       return FollowState.Following
     }
     return FollowState.NotFollowing
@@ -53,49 +96,41 @@ export class MyFollowsCache {
   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)
-    }
+    this.hydrate(did, res.data)
     return this.getFollowState(did)
   }
 
   getFollowUri(did: string): string {
-    const v = this.followDidToRecordMap[did]
+    const v = this.byDid[did]
     if (typeof v === 'string') {
       return v
     }
     throw new Error('Not a followed user')
   }
 
-  addFollow(did: string, recordUri: string) {
-    this.followDidToRecordMap[did] = recordUri
+  addFollow(did: string, info: FollowInfo) {
+    this.byDid[did] = info
   }
 
   removeFollow(did: string) {
-    this.followDidToRecordMap[did] = false
+    if (this.byDid[did]) {
+      this.byDid[did].followRecordUri = undefined
+    }
   }
 
-  /**
-   * Use this to incrementally update the cache as views provide information
-   */
-  hydrate(did: string, recordUri: string | undefined) {
-    if (recordUri) {
-      this.followDidToRecordMap[did] = recordUri
-    } else {
-      this.followDidToRecordMap[did] = false
+  hydrate(did: string, profile: Profile) {
+    this.byDid[did] = {
+      did,
+      followRecordUri: profile.viewer?.following,
+      handle: profile.handle,
+      displayName: profile.displayName,
+      avatar: profile.avatar,
     }
   }
 
-  /**
-   * Use this to incrementally update the cache as views provide information
-   */
-  hydrateProfiles(profiles: Profile[]) {
+  hydrateMany(profiles: Profile[]) {
     for (const profile of profiles) {
-      if (profile.viewer) {
-        this.hydrate(profile.did, profile.viewer.following)
-      }
+      this.hydrate(profile.did, profile)
     }
   }
 }