about summary refs log tree commit diff
path: root/src/lib/api/feed/home.ts
blob: 7a0d72d91535a10477b58bed96e787635bf9435d (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api'

import {PROD_DEFAULT_FEED} from '#/lib/constants'
import {CustomFeedAPI} from './custom'
import {FollowingFeedAPI} from './following'
import {type FeedAPI, type FeedAPIResponse} from './types'

// HACK
// the feed API does not include any facilities for passing down
// non-post elements. adding that is a bit of a heavy lift, and we
// have just one temporary usecase for it: flagging when the home feed
// falls back to discover.
// we use this fallback marker post to drive this instead. see Feed.tsx
// for the usage.
// -prf
export const FALLBACK_MARKER_POST: AppBskyFeedDefs.FeedViewPost = {
  post: {
    uri: 'fallback-marker-post',
    cid: 'fake',
    record: {},
    author: {
      did: 'did:fake',
      handle: 'fake.com',
    },
    indexedAt: new Date().toISOString(),
  },
}

export class HomeFeedAPI implements FeedAPI {
  agent: BskyAgent
  following: FollowingFeedAPI
  discover: CustomFeedAPI
  usingDiscover = false
  itemCursor = 0
  userInterests?: string

  constructor({
    userInterests,
    agent,
  }: {
    userInterests?: string
    agent: BskyAgent
  }) {
    this.agent = agent
    this.following = new FollowingFeedAPI({agent})
    this.discover = new CustomFeedAPI({
      agent,
      feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
    })
    this.userInterests = userInterests
  }

  reset() {
    this.following = new FollowingFeedAPI({agent: this.agent})
    this.discover = new CustomFeedAPI({
      agent: this.agent,
      feedParams: {feed: PROD_DEFAULT_FEED('whats-hot')},
      userInterests: this.userInterests,
    })
    this.usingDiscover = false
    this.itemCursor = 0
  }

  async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
    if (this.usingDiscover) {
      return this.discover.peekLatest()
    }
    return this.following.peekLatest()
  }

  async fetch({
    cursor,
    limit,
  }: {
    cursor: string | undefined
    limit: number
  }): Promise<FeedAPIResponse> {
    if (!cursor) {
      this.reset()
    }

    let returnCursor
    let posts: AppBskyFeedDefs.FeedViewPost[] = []

    if (!this.usingDiscover) {
      const res = await this.following.fetch({cursor, limit})
      returnCursor = res.cursor
      posts = posts.concat(res.feed)
      if (!returnCursor) {
        cursor = ''
        posts.push(FALLBACK_MARKER_POST)
        this.usingDiscover = true
      }
    }

    if (this.usingDiscover && !__DEV__) {
      const res = await this.discover.fetch({cursor, limit})
      returnCursor = res.cursor
      posts = posts.concat(res.feed)
    }

    return {
      cursor: returnCursor,
      feed: posts,
    }
  }
}