about summary refs log tree commit diff
path: root/src/lib
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/api/feed-manip.ts35
-rw-r--r--src/lib/api/feed/author.ts11
-rw-r--r--src/lib/api/feed/custom.ts11
-rw-r--r--src/lib/api/feed/following.ts9
-rw-r--r--src/lib/api/feed/likes.ts11
-rw-r--r--src/lib/api/feed/list.ts11
-rw-r--r--src/lib/api/feed/merge.ts40
-rw-r--r--src/lib/constants.ts4
-rw-r--r--src/lib/hooks/useAccountSwitcher.ts24
-rw-r--r--src/lib/notifications/notifications.ts4
-rw-r--r--src/lib/react-query.ts9
-rw-r--r--src/lib/sentry.ts42
12 files changed, 128 insertions, 83 deletions
diff --git a/src/lib/api/feed-manip.ts b/src/lib/api/feed-manip.ts
index 912302d0a..1123c4e23 100644
--- a/src/lib/api/feed-manip.ts
+++ b/src/lib/api/feed-manip.ts
@@ -16,14 +16,7 @@ export type FeedTunerFn = (
 export class FeedViewPostsSlice {
   isFlattenedReply = false
 
-  constructor(public items: FeedViewPost[] = []) {}
-
-  get _reactKey() {
-    const rootItem = this.isFlattenedReply ? this.items[1] : this.items[0]
-    return `slice-${rootItem.post.uri}-${
-      rootItem.reason?.indexedAt || rootItem.post.indexedAt
-    }`
-  }
+  constructor(public items: FeedViewPost[], public _reactKey: string) {}
 
   get uri() {
     if (this.isFlattenedReply) {
@@ -118,28 +111,34 @@ export class FeedViewPostsSlice {
 }
 
 export class NoopFeedTuner {
-  reset() {}
+  private keyCounter = 0
+
+  reset() {
+    this.keyCounter = 0
+  }
   tune(
     feed: FeedViewPost[],
-    _tunerFns: FeedTunerFn[] = [],
     _opts?: {dryRun: boolean; maintainOrder: boolean},
   ): FeedViewPostsSlice[] {
-    return feed.map(item => new FeedViewPostsSlice([item]))
+    return feed.map(
+      item => new FeedViewPostsSlice([item], `slice-${this.keyCounter++}`),
+    )
   }
 }
 
 export class FeedTuner {
+  private keyCounter = 0
   seenUris: Set<string> = new Set()
 
-  constructor() {}
+  constructor(public tunerFns: FeedTunerFn[]) {}
 
   reset() {
+    this.keyCounter = 0
     this.seenUris.clear()
   }
 
   tune(
     feed: FeedViewPost[],
-    tunerFns: FeedTunerFn[] = [],
     {dryRun, maintainOrder}: {dryRun: boolean; maintainOrder: boolean} = {
       dryRun: false,
       maintainOrder: false,
@@ -148,7 +147,9 @@ export class FeedTuner {
     let slices: FeedViewPostsSlice[] = []
 
     if (maintainOrder) {
-      slices = feed.map(item => new FeedViewPostsSlice([item]))
+      slices = feed.map(
+        item => new FeedViewPostsSlice([item], `slice-${this.keyCounter++}`),
+      )
     } else {
       // arrange the posts into thread slices
       for (let i = feed.length - 1; i >= 0; i--) {
@@ -164,12 +165,14 @@ export class FeedTuner {
             continue
           }
         }
-        slices.unshift(new FeedViewPostsSlice([item]))
+        slices.unshift(
+          new FeedViewPostsSlice([item], `slice-${this.keyCounter++}`),
+        )
       }
     }
 
     // run the custom tuners
-    for (const tunerFn of tunerFns) {
+    for (const tunerFn of this.tunerFns) {
       slices = tunerFn(this, slices.slice())
     }
 
diff --git a/src/lib/api/feed/author.ts b/src/lib/api/feed/author.ts
index 77c167869..92df84f8b 100644
--- a/src/lib/api/feed/author.ts
+++ b/src/lib/api/feed/author.ts
@@ -1,18 +1,15 @@
 import {
   AppBskyFeedDefs,
   AppBskyFeedGetAuthorFeed as GetAuthorFeed,
-  BskyAgent,
 } from '@atproto/api'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class AuthorFeedAPI implements FeedAPI {
-  constructor(
-    public agent: BskyAgent,
-    public params: GetAuthorFeed.QueryParams,
-  ) {}
+  constructor(public params: GetAuthorFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.agent.getAuthorFeed({
+    const res = await getAgent().getAuthorFeed({
       ...this.params,
       limit: 1,
     })
@@ -26,7 +23,7 @@ export class AuthorFeedAPI implements FeedAPI {
     cursor: string | undefined
     limit: number
   }): Promise<FeedAPIResponse> {
-    const res = await this.agent.getAuthorFeed({
+    const res = await getAgent().getAuthorFeed({
       ...this.params,
       cursor,
       limit,
diff --git a/src/lib/api/feed/custom.ts b/src/lib/api/feed/custom.ts
index 0be98fb4a..47ffc65ed 100644
--- a/src/lib/api/feed/custom.ts
+++ b/src/lib/api/feed/custom.ts
@@ -1,18 +1,15 @@
 import {
   AppBskyFeedDefs,
   AppBskyFeedGetFeed as GetCustomFeed,
-  BskyAgent,
 } from '@atproto/api'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class CustomFeedAPI implements FeedAPI {
-  constructor(
-    public agent: BskyAgent,
-    public params: GetCustomFeed.QueryParams,
-  ) {}
+  constructor(public params: GetCustomFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.agent.app.bsky.feed.getFeed({
+    const res = await getAgent().app.bsky.feed.getFeed({
       ...this.params,
       limit: 1,
     })
@@ -26,7 +23,7 @@ export class CustomFeedAPI implements FeedAPI {
     cursor: string | undefined
     limit: number
   }): Promise<FeedAPIResponse> {
-    const res = await this.agent.app.bsky.feed.getFeed({
+    const res = await getAgent().app.bsky.feed.getFeed({
       ...this.params,
       cursor,
       limit,
diff --git a/src/lib/api/feed/following.ts b/src/lib/api/feed/following.ts
index 13f06c7ab..24389b5ed 100644
--- a/src/lib/api/feed/following.ts
+++ b/src/lib/api/feed/following.ts
@@ -1,11 +1,12 @@
-import {AppBskyFeedDefs, BskyAgent} from '@atproto/api'
+import {AppBskyFeedDefs} from '@atproto/api'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class FollowingFeedAPI implements FeedAPI {
-  constructor(public agent: BskyAgent) {}
+  constructor() {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.agent.getTimeline({
+    const res = await getAgent().getTimeline({
       limit: 1,
     })
     return res.data.feed[0]
@@ -18,7 +19,7 @@ export class FollowingFeedAPI implements FeedAPI {
     cursor: string | undefined
     limit: number
   }): Promise<FeedAPIResponse> {
-    const res = await this.agent.getTimeline({
+    const res = await getAgent().getTimeline({
       cursor,
       limit,
     })
diff --git a/src/lib/api/feed/likes.ts b/src/lib/api/feed/likes.ts
index 434ed7719..2b0afdf11 100644
--- a/src/lib/api/feed/likes.ts
+++ b/src/lib/api/feed/likes.ts
@@ -1,18 +1,15 @@
 import {
   AppBskyFeedDefs,
   AppBskyFeedGetActorLikes as GetActorLikes,
-  BskyAgent,
 } from '@atproto/api'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class LikesFeedAPI implements FeedAPI {
-  constructor(
-    public agent: BskyAgent,
-    public params: GetActorLikes.QueryParams,
-  ) {}
+  constructor(public params: GetActorLikes.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.agent.getActorLikes({
+    const res = await getAgent().getActorLikes({
       ...this.params,
       limit: 1,
     })
@@ -26,7 +23,7 @@ export class LikesFeedAPI implements FeedAPI {
     cursor: string | undefined
     limit: number
   }): Promise<FeedAPIResponse> {
-    const res = await this.agent.getActorLikes({
+    const res = await getAgent().getActorLikes({
       ...this.params,
       cursor,
       limit,
diff --git a/src/lib/api/feed/list.ts b/src/lib/api/feed/list.ts
index 6cb0730e7..19f2ff177 100644
--- a/src/lib/api/feed/list.ts
+++ b/src/lib/api/feed/list.ts
@@ -1,18 +1,15 @@
 import {
   AppBskyFeedDefs,
   AppBskyFeedGetListFeed as GetListFeed,
-  BskyAgent,
 } from '@atproto/api'
 import {FeedAPI, FeedAPIResponse} from './types'
+import {getAgent} from '#/state/session'
 
 export class ListFeedAPI implements FeedAPI {
-  constructor(
-    public agent: BskyAgent,
-    public params: GetListFeed.QueryParams,
-  ) {}
+  constructor(public params: GetListFeed.QueryParams) {}
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.agent.app.bsky.feed.getListFeed({
+    const res = await getAgent().app.bsky.feed.getListFeed({
       ...this.params,
       limit: 1,
     })
@@ -26,7 +23,7 @@ export class ListFeedAPI implements FeedAPI {
     cursor: string | undefined
     limit: number
   }): Promise<FeedAPIResponse> {
-    const res = await this.agent.app.bsky.feed.getListFeed({
+    const res = await getAgent().app.bsky.feed.getListFeed({
       ...this.params,
       cursor,
       limit,
diff --git a/src/lib/api/feed/merge.ts b/src/lib/api/feed/merge.ts
index 7a0f02887..11e963f0a 100644
--- a/src/lib/api/feed/merge.ts
+++ b/src/lib/api/feed/merge.ts
@@ -1,4 +1,4 @@
-import {AppBskyFeedDefs, AppBskyFeedGetTimeline, BskyAgent} from '@atproto/api'
+import {AppBskyFeedDefs, AppBskyFeedGetTimeline} from '@atproto/api'
 import shuffle from 'lodash.shuffle'
 import {timeout} from 'lib/async/timeout'
 import {bundleAsync} from 'lib/async/bundle'
@@ -7,6 +7,7 @@ import {FeedTuner} from '../feed-manip'
 import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types'
 import {FeedParams} from '#/state/queries/post-feed'
 import {FeedTunerFn} from '../feed-manip'
+import {getAgent} from '#/state/session'
 
 const REQUEST_WAIT_MS = 500 // 500ms
 const POST_AGE_CUTOFF = 60e3 * 60 * 24 // 24hours
@@ -18,16 +19,12 @@ export class MergeFeedAPI implements FeedAPI {
   itemCursor = 0
   sampleCursor = 0
 
-  constructor(
-    public agent: BskyAgent,
-    public params: FeedParams,
-    public feedTuners: FeedTunerFn[],
-  ) {
-    this.following = new MergeFeedSource_Following(this.agent, this.feedTuners)
+  constructor(public params: FeedParams, public feedTuners: FeedTunerFn[]) {
+    this.following = new MergeFeedSource_Following(this.feedTuners)
   }
 
   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.feedCursor = 0
     this.itemCursor = 0
@@ -35,8 +32,7 @@ export class MergeFeedAPI implements FeedAPI {
     if (this.params.mergeFeedEnabled && this.params.mergeFeedSources) {
       this.customFeeds = shuffle(
         this.params.mergeFeedSources.map(
-          feedUri =>
-            new MergeFeedSource_Custom(this.agent, feedUri, this.feedTuners),
+          feedUri => new MergeFeedSource_Custom(feedUri, this.feedTuners),
         ),
       )
     } else {
@@ -45,7 +41,7 @@ export class MergeFeedAPI implements FeedAPI {
   }
 
   async peekLatest(): Promise<AppBskyFeedDefs.FeedViewPost> {
-    const res = await this.agent.getTimeline({
+    const res = await getAgent().getTimeline({
       limit: 1,
     })
     return res.data.feed[0]
@@ -137,7 +133,7 @@ class MergeFeedSource {
   queue: AppBskyFeedDefs.FeedViewPost[] = []
   hasMore = true
 
-  constructor(public agent: BskyAgent, public feedTuners: FeedTunerFn[]) {}
+  constructor(public feedTuners: FeedTunerFn[]) {}
 
   get numReady() {
     return this.queue.length
@@ -184,7 +180,7 @@ class MergeFeedSource {
 }
 
 class MergeFeedSource_Following extends MergeFeedSource {
-  tuner = new FeedTuner()
+  tuner = new FeedTuner(this.feedTuners)
 
   reset() {
     super.reset()
@@ -199,9 +195,9 @@ class MergeFeedSource_Following extends MergeFeedSource {
     cursor: string | undefined,
     limit: number,
   ): 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
-    const slices = this.tuner.tune(res.data.feed, this.feedTuners, {
+    const slices = this.tuner.tune(res.data.feed, {
       dryRun: false,
       maintainOrder: true,
     })
@@ -213,20 +209,16 @@ class MergeFeedSource_Following extends MergeFeedSource {
 class MergeFeedSource_Custom extends MergeFeedSource {
   minDate: Date
 
-  constructor(
-    public agent: BskyAgent,
-    public feedUri: string,
-    public feedTuners: FeedTunerFn[],
-  ) {
-    super(agent, feedTuners)
+  constructor(public feedUri: string, public feedTuners: FeedTunerFn[]) {
+    super(feedTuners)
     this.sourceInfo = {
       $type: 'reasonFeedSource',
       displayName: feedUri.split('/').pop() || '',
       uri: feedUriToHref(feedUri),
     }
     this.minDate = new Date(Date.now() - POST_AGE_CUTOFF)
-    this.agent.app.bsky.feed
-      .getFeedGenerator({
+    getAgent()
+      .app.bsky.feed.getFeedGenerator({
         feed: feedUri,
       })
       .then(
@@ -244,7 +236,7 @@ class MergeFeedSource_Custom extends MergeFeedSource {
     limit: number,
   ): Promise<AppBskyFeedGetTimeline.Response> {
     try {
-      const res = await this.agent.app.bsky.feed.getFeed({
+      const res = await getAgent().app.bsky.feed.getFeed({
         cursor,
         limit,
         feed: this.feedUri,
diff --git a/src/lib/constants.ts b/src/lib/constants.ts
index f8f651305..aa5983be7 100644
--- a/src/lib/constants.ts
+++ b/src/lib/constants.ts
@@ -116,8 +116,8 @@ export async function DEFAULT_FEEDS(
   } else {
     // production
     return {
-      pinned: [PROD_DEFAULT_FEED('whats-hot')],
-      saved: [PROD_DEFAULT_FEED('whats-hot')],
+      pinned: [],
+      saved: [],
     }
   }
 }
diff --git a/src/lib/hooks/useAccountSwitcher.ts b/src/lib/hooks/useAccountSwitcher.ts
index 82f4565e9..8a1dea5fe 100644
--- a/src/lib/hooks/useAccountSwitcher.ts
+++ b/src/lib/hooks/useAccountSwitcher.ts
@@ -7,22 +7,35 @@ import {useAnalytics} from '#/lib/analytics/analytics'
 import {useSessionApi, SessionAccount} from '#/state/session'
 import * as Toast from '#/view/com/util/Toast'
 import {useCloseAllActiveElements} from '#/state/util'
+import {useLoggedOutViewControls} from '#/state/shell/logged-out'
 
 export function useAccountSwitcher() {
   const {track} = useAnalytics()
   const {selectAccount, clearCurrentAccount} = useSessionApi()
   const closeAllActiveElements = useCloseAllActiveElements()
   const navigation = useNavigation<NavigationProp>()
+  const {setShowLoggedOut} = useLoggedOutViewControls()
 
   const onPressSwitchAccount = useCallback(
-    async (acct: SessionAccount) => {
+    async (account: SessionAccount) => {
       track('Settings:SwitchAccountButtonClicked')
 
       try {
-        closeAllActiveElements()
-        navigation.navigate(isWeb ? 'Home' : 'HomeTab')
-        await selectAccount(acct)
-        Toast.show(`Signed in as ${acct.handle}`)
+        if (account.accessJwt) {
+          closeAllActiveElements()
+          navigation.navigate(isWeb ? 'Home' : 'HomeTab')
+          await selectAccount(account)
+          setTimeout(() => {
+            Toast.show(`Signed in as @${account.handle}`)
+          }, 100)
+        } else {
+          closeAllActiveElements()
+          setShowLoggedOut(true)
+          Toast.show(
+            `Please sign in as @${account.handle}`,
+            'circle-exclamation',
+          )
+        }
       } catch (e) {
         Toast.show('Sorry! We need you to enter your password.')
         clearCurrentAccount() // back user out to login
@@ -34,6 +47,7 @@ export function useAccountSwitcher() {
       selectAccount,
       closeAllActiveElements,
       navigation,
+      setShowLoggedOut,
     ],
   )
 
diff --git a/src/lib/notifications/notifications.ts b/src/lib/notifications/notifications.ts
index 2320e1c7b..9c499be08 100644
--- a/src/lib/notifications/notifications.ts
+++ b/src/lib/notifications/notifications.ts
@@ -83,7 +83,7 @@ export function init(queryClient: QueryClient) {
     )
     if (event.request.trigger.type === 'push') {
       // refresh notifications in the background
-      queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS()})
+      queryClient.resetQueries({queryKey: RQKEY_NOTIFS()})
       // handle payload-based deeplinks
       let payload
       if (isIOS) {
@@ -121,7 +121,7 @@ export function init(queryClient: QueryClient) {
           logger.DebugContext.notifications,
         )
         track('Notificatons:OpenApp')
-        queryClient.invalidateQueries({queryKey: RQKEY_NOTIFS()})
+        queryClient.resetQueries({queryKey: RQKEY_NOTIFS()})
         resetToTab('NotificationsTab') // open notifications tab
       }
     },
diff --git a/src/lib/react-query.ts b/src/lib/react-query.ts
index 6b425d3b4..6ec620f74 100644
--- a/src/lib/react-query.ts
+++ b/src/lib/react-query.ts
@@ -8,6 +8,15 @@ export const queryClient = new QueryClient({
       // so we NEVER want to enable this
       // -prf
       refetchOnWindowFocus: false,
+      // Structural sharing between responses makes it impossible to rely on
+      // "first seen" timestamps on objects to determine if they're fresh.
+      // Disable this optimization so that we can rely on "first seen" timestamps.
+      structuralSharing: false,
+      // We don't want to retry queries by default, because in most cases we
+      // want to fail early and show a response to the user. There are
+      // exceptions, and those can be made on a per-query basis. For others, we
+      // should give users controls to retry.
+      retry: false,
     },
   },
 })
diff --git a/src/lib/sentry.ts b/src/lib/sentry.ts
index b080bcc5c..63a21a43c 100644
--- a/src/lib/sentry.ts
+++ b/src/lib/sentry.ts
@@ -1,8 +1,46 @@
+/**
+ * Importing these separately from `platform/detection` and `lib/app-info` to
+ * avoid future conflicts and/or circular deps
+ */
+
+import {Platform} from 'react-native'
+import app from 'react-native-version-number'
+import * as info from 'expo-updates'
 import {init} from 'sentry-expo'
 
+/**
+ * Matches the build profile `channel` props in `eas.json`
+ */
+const buildChannel = (info.channel || 'development') as
+  | 'development'
+  | 'preview'
+  | 'production'
+
+/**
+ * Examples:
+ * - `dev`
+ * - `1.57.0`
+ */
+const release = app.appVersion ?? 'dev'
+
+/**
+ * Examples:
+ * - `web.dev`
+ * - `ios.dev`
+ * - `android.dev`
+ * - `web.1.57.0`
+ * - `ios.1.57.0.3`
+ * - `android.1.57.0.46`
+ */
+const dist = `${Platform.OS}.${release}${
+  app.buildVersion ? `.${app.buildVersion}` : ''
+}`
+
 init({
   dsn: 'https://05bc3789bf994b81bd7ce20c86ccd3ae@o4505071687041024.ingest.sentry.io/4505071690514432',
-  enableInExpoDevelopment: false, // if true, Sentry will try to send events/errors in development mode.
   debug: false, // If `true`, Sentry will try to print out useful debugging information if something goes wrong with sending the event. Set it to `false` in production
-  environment: __DEV__ ? 'development' : 'production', // Set the environment
+  enableInExpoDevelopment: true,
+  environment: buildChannel,
+  dist,
+  release,
 })