diff options
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/api/feed-manip.ts | 35 | ||||
-rw-r--r-- | src/lib/api/feed/author.ts | 11 | ||||
-rw-r--r-- | src/lib/api/feed/custom.ts | 11 | ||||
-rw-r--r-- | src/lib/api/feed/following.ts | 9 | ||||
-rw-r--r-- | src/lib/api/feed/likes.ts | 11 | ||||
-rw-r--r-- | src/lib/api/feed/list.ts | 11 | ||||
-rw-r--r-- | src/lib/api/feed/merge.ts | 40 | ||||
-rw-r--r-- | src/lib/constants.ts | 4 | ||||
-rw-r--r-- | src/lib/hooks/useAccountSwitcher.ts | 24 | ||||
-rw-r--r-- | src/lib/notifications/notifications.ts | 4 | ||||
-rw-r--r-- | src/lib/react-query.ts | 9 | ||||
-rw-r--r-- | src/lib/sentry.ts | 42 |
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, }) |