diff --git a/src/lib/strings.ts b/src/lib/strings.ts index 8b93fa9333..a2d3f502a7 100644 --- a/src/lib/strings.ts +++ b/src/lib/strings.ts @@ -198,7 +198,7 @@ export function enforceLen(str: string, len: number, ellipsis = false): string { export function cleanError(str: any): string { if (!str) { - return str + return '' } if (typeof str !== 'string') { str = str.toString() @@ -206,6 +206,9 @@ export function cleanError(str: any): string { if (isNetworkError(str)) { return 'Unable to connect. Please check your internet connection and try again.' } + if (str.includes('Upstream Failure')) { + return 'The server appears to be experiencing issues. Please try again in a few moments.' + } if (str.startsWith('Error: ')) { return str.slice('Error: '.length) } diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts index bab308e989..e25445314e 100644 --- a/src/state/models/feed-view.ts +++ b/src/state/models/feed-view.ts @@ -378,7 +378,7 @@ export class FeedModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err ? cleanError(err.toString()) : '' + this.error = cleanError(err) if (err) { this.rootStore.log.error('Posts feed request failed', err) } @@ -420,7 +420,11 @@ export class FeedModel { await this._prependAll(res) this._xIdle() } catch (e: any) { - this._xIdle(e) + this._xIdle() // don't bubble the error to the user + this.rootStore.log.error('FeedView: Failed to load latest', { + params: this.params, + e, + }) } } @@ -437,7 +441,11 @@ export class FeedModel { await this._appendAll(res) this._xIdle() } catch (e: any) { - this._xIdle(e) + this._xIdle() // don't bubble the error to the user + this.rootStore.log.error('FeedView: Failed to load more', { + params: this.params, + e, + }) } } @@ -463,7 +471,11 @@ export class FeedModel { } while (cursor && numToFetch > 0) this._xIdle() } catch (e: any) { - this._xIdle(e) + this._xIdle() // don't bubble the error to the user + this.rootStore.log.error('FeedView: Failed to update', { + params: this.params, + e, + }) } } diff --git a/src/state/models/get-assertions-view.ts b/src/state/models/get-assertions-view.ts deleted file mode 100644 index bdb2c0894b..0000000000 --- a/src/state/models/get-assertions-view.ts +++ /dev/null @@ -1,123 +0,0 @@ -import {makeAutoObservable} from 'mobx' -import {AppBskyGraphGetAssertions as GetAssertions} from '@atproto/api' -import {RootStoreModel} from './root-store' - -export type Assertion = GetAssertions.Assertion & { - _reactKey: string -} - -export class GetAssertionsView { - // state - isLoading = false - isRefreshing = false - hasLoaded = false - error = '' - params: GetAssertions.QueryParams - - // data - assertions: Assertion[] = [] - - constructor( - public rootStore: RootStoreModel, - params: GetAssertions.QueryParams, - ) { - makeAutoObservable( - this, - { - rootStore: false, - params: false, - }, - {autoBind: true}, - ) - this.params = params - } - - get hasContent() { - return this.assertions.length > 0 - } - - get hasError() { - return this.error !== '' - } - - get isEmpty() { - return this.hasLoaded && !this.hasContent - } - - getBySubject(did: string) { - return this.assertions.find(assertion => assertion.subject.did === did) - } - - get confirmed() { - return this.assertions.filter(assertion => !!assertion.confirmation) - } - - get unconfirmed() { - return this.assertions.filter(assertion => !assertion.confirmation) - } - - // public api - // = - - async setup() { - await this._fetch() - } - - async refresh() { - await this._fetch(true) - } - - async loadMore() { - // TODO - } - - // state transitions - // = - - private _xLoading(isRefreshing = false) { - this.isLoading = true - this.isRefreshing = isRefreshing - this.error = '' - } - - private _xIdle(err?: any) { - this.isLoading = false - this.isRefreshing = false - this.hasLoaded = true - this.error = err ? err.toString() : '' - if (err) { - this.rootStore.log.error('Failed to fetch assertions', err) - } - } - - // loader functions - // = - - private async _fetch(isRefreshing = false) { - this._xLoading(isRefreshing) - try { - const res = await this.rootStore.api.app.bsky.graph.getAssertions( - this.params, - ) - this._replaceAll(res) - this._xIdle() - } catch (e: any) { - this._xIdle(e) - } - } - - private _replaceAll(res: GetAssertions.Response) { - this.assertions.length = 0 - let counter = 0 - for (const item of res.data.assertions) { - this._append({ - _reactKey: `item-${counter++}`, - ...item, - }) - } - } - - private _append(item: Assertion) { - this.assertions.push(item) - } -} diff --git a/src/state/models/notifications-view.ts b/src/state/models/notifications-view.ts index 93b6a398f6..2c1c39b497 100644 --- a/src/state/models/notifications-view.ts +++ b/src/state/models/notifications-view.ts @@ -329,7 +329,6 @@ export class NotificationsViewModel { this.isRefreshing = false this.hasLoaded = true this.error = cleanError(err) - this.error = err ? cleanError(err) : '' if (err) { this.rootStore.log.error('Failed to fetch notifications', err) } @@ -378,7 +377,11 @@ export class NotificationsViewModel { await this._appendAll(res) this._xIdle() } catch (e: any) { - this._xIdle(e) + this._xIdle() // don't bubble the error to the user + this.rootStore.log.error('NotificationsView: Failed to load more', { + params: this.params, + e, + }) } } @@ -405,7 +408,11 @@ export class NotificationsViewModel { } while (cursor && numToFetch > 0) this._xIdle() } catch (e: any) { - this._xIdle(e) + this._xIdle() // don't bubble the error to the user + this.rootStore.log.error('NotificationsView: Failed to update', { + params: this.params, + e, + }) } } diff --git a/src/state/models/post-thread-view.ts b/src/state/models/post-thread-view.ts index 584658e144..251fd1b445 100644 --- a/src/state/models/post-thread-view.ts +++ b/src/state/models/post-thread-view.ts @@ -6,6 +6,7 @@ import { import {AtUri} from '../../third-party/uri' import {RootStoreModel} from './root-store' import * as apilib from '../lib/api' +import {cleanError} from '../../lib/strings' function* reactKeyGenerator(): Generator { let counter = 0 @@ -276,7 +277,7 @@ export class PostThreadViewModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err ? err.toString() : '' + this.error = cleanError(err) if (err) { this.rootStore.log.error('Failed to fetch post thread', err) } diff --git a/src/state/models/post.ts b/src/state/models/post.ts index 497c8e4c92..04107dd48b 100644 --- a/src/state/models/post.ts +++ b/src/state/models/post.ts @@ -67,7 +67,6 @@ export class PostModel implements RemoveIndex { this.isLoading = false this.hasLoaded = true this.error = cleanError(err) - this.error = err ? cleanError(err) : '' if (err) { this.rootStore.log.error('Failed to fetch post', err) } diff --git a/src/state/models/profile-view.ts b/src/state/models/profile-view.ts index a1535693c9..df86ebd9c6 100644 --- a/src/state/models/profile-view.ts +++ b/src/state/models/profile-view.ts @@ -11,6 +11,7 @@ type Entity = AppBskyFeedPost.Entity import {extractEntities} from '../../lib/strings' import {RootStoreModel} from './root-store' import * as apilib from '../lib/api' +import {cleanError} from '../../lib/strings' export const ACTOR_TYPE_USER = 'app.bsky.system.actorUser' @@ -175,7 +176,7 @@ export class ProfileViewModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err ? err.toString() : '' + this.error = cleanError(err) if (err) { this.rootStore.log.error('Failed to fetch profile', err) } diff --git a/src/state/models/reposted-by-view.ts b/src/state/models/reposted-by-view.ts index 1de6b7c58b..5d7f4a3380 100644 --- a/src/state/models/reposted-by-view.ts +++ b/src/state/models/reposted-by-view.ts @@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx' import {AtUri} from '../../third-party/uri' import {AppBskyFeedGetRepostedBy as GetRepostedBy} from '@atproto/api' import {RootStoreModel} from './root-store' +import {cleanError} from '../../lib/strings' const PAGE_SIZE = 30 @@ -82,7 +83,7 @@ export class RepostedByViewModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err ? err.toString() : '' + this.error = cleanError(err) if (err) { this.rootStore.log.error('Failed to fetch reposted by view', err) } diff --git a/src/state/models/suggested-actors-view.ts b/src/state/models/suggested-actors-view.ts index ebc9e8bc20..421ac0f182 100644 --- a/src/state/models/suggested-actors-view.ts +++ b/src/state/models/suggested-actors-view.ts @@ -1,6 +1,7 @@ import {makeAutoObservable, runInAction} from 'mobx' import {AppBskyActorGetSuggestions as GetSuggestions} from '@atproto/api' import {RootStoreModel} from './root-store' +import {cleanError} from '../../lib/strings' const PAGE_SIZE = 30 @@ -70,7 +71,7 @@ export class SuggestedActorsViewModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err ? err.toString() : '' + this.error = cleanError(err) if (err) { this.rootStore.log.error('Failed to fetch suggested actors', err) } diff --git a/src/state/models/user-followers-view.ts b/src/state/models/user-followers-view.ts index 9daaf35a4b..b6e12f64d6 100644 --- a/src/state/models/user-followers-view.ts +++ b/src/state/models/user-followers-view.ts @@ -4,6 +4,7 @@ import { AppBskyActorRef as ActorRef, } from '@atproto/api' import {RootStoreModel} from './root-store' +import {cleanError} from '../../lib/strings' const PAGE_SIZE = 30 @@ -84,7 +85,7 @@ export class UserFollowersViewModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err ? err.toString() : '' + this.error = cleanError(err) if (err) { this.rootStore.log.error('Failed to fetch user followers', err) } diff --git a/src/state/models/user-follows-view.ts b/src/state/models/user-follows-view.ts index d43a10c75f..db9d86152c 100644 --- a/src/state/models/user-follows-view.ts +++ b/src/state/models/user-follows-view.ts @@ -4,6 +4,7 @@ import { AppBskyActorRef as ActorRef, } from '@atproto/api' import {RootStoreModel} from './root-store' +import {cleanError} from '../../lib/strings' const PAGE_SIZE = 30 @@ -84,7 +85,7 @@ export class UserFollowsViewModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err ? err.toString() : '' + this.error = cleanError(err) if (err) { this.rootStore.log.error('Failed to fetch user follows', err) } diff --git a/src/state/models/votes-view.ts b/src/state/models/votes-view.ts index df939226fe..76eb52e6e5 100644 --- a/src/state/models/votes-view.ts +++ b/src/state/models/votes-view.ts @@ -2,6 +2,7 @@ import {makeAutoObservable, runInAction} from 'mobx' import {AtUri} from '../../third-party/uri' import {AppBskyFeedGetVotes as GetVotes} from '@atproto/api' import {RootStoreModel} from './root-store' +import {cleanError} from '../../lib/strings' const PAGE_SIZE = 30 @@ -79,7 +80,7 @@ export class VotesViewModel { this.isLoading = false this.isRefreshing = false this.hasLoaded = true - this.error = err ? err.toString() : '' + this.error = cleanError(err) if (err) { this.rootStore.log.error('Failed to fetch votes', err) } diff --git a/src/view/com/login/CreateAccount.tsx b/src/view/com/login/CreateAccount.tsx index c912837825..4a3245ad0e 100644 --- a/src/view/com/login/CreateAccount.tsx +++ b/src/view/com/login/CreateAccount.tsx @@ -27,6 +27,7 @@ import {useStores, DEFAULT_SERVICE} from '../../../state' import {ServiceDescription} from '../../../state/models/session' import {ServerInputModal} from '../../../state/models/shell-ui' import {usePalette} from '../../lib/hooks/usePalette' +import {cleanError} from '../../../lib/strings' export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { const {track, screen} = useAnalytics() @@ -119,7 +120,7 @@ export const CreateAccount = ({onPressBack}: {onPressBack: () => void}) => { } store.log.error('Failed to create account', e) setIsProcessing(false) - setError(errMsg.replace(/^Error:/, '')) + setError(cleanError(errMsg)) } } diff --git a/src/view/com/login/Signin.tsx b/src/view/com/login/Signin.tsx index 0c1f0620ca..ea968cdf2c 100644 --- a/src/view/com/login/Signin.tsx +++ b/src/view/com/login/Signin.tsx @@ -23,6 +23,7 @@ import {ServerInputModal} from '../../../state/models/shell-ui' import {AccountData} from '../../../state/models/session' import {isNetworkError} from '../../../lib/errors' import {usePalette} from '../../lib/hooks/usePalette' +import {cleanError} from '../../../lib/strings' enum Forms { Login, @@ -322,7 +323,7 @@ const LoginForm = ({ 'Unable to contact your service. Please check your Internet connection.', ) } else { - setError(errMsg.replace(/^Error:/, '')) + setError(cleanError(errMsg)) } } } @@ -499,7 +500,7 @@ const ForgotPasswordForm = ({ 'Unable to contact your service. Please check your Internet connection.', ) } else { - setError(errMsg.replace(/^Error:/, '')) + setError(cleanError(errMsg)) } } } @@ -636,7 +637,7 @@ const SetNewPasswordForm = ({ 'Unable to contact your service. Please check your Internet connection.', ) } else { - setError(errMsg.replace(/^Error:/, '')) + setError(cleanError(errMsg)) } } } diff --git a/src/view/com/modals/Confirm.tsx b/src/view/com/modals/Confirm.tsx index 3e2ad6eea4..929e684fc8 100644 --- a/src/view/com/modals/Confirm.tsx +++ b/src/view/com/modals/Confirm.tsx @@ -10,6 +10,7 @@ import {Text} from '../util/text/Text' import {useStores} from '../../../state' import {s, colors, gradients} from '../../lib/styles' import {ErrorMessage} from '../util/error/ErrorMessage' +import {cleanError} from '../../../lib/strings' export const snapPoints = ['50%'] @@ -33,7 +34,7 @@ export function Component({ store.shell.closeModal() return } catch (e: any) { - setError(e.toString()) + setError(cleanError(e)) setIsProcessing(false) } } diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx index 15bf7115ea..c732a4ed51 100644 --- a/src/view/com/modals/EditProfile.tsx +++ b/src/view/com/modals/EditProfile.tsx @@ -25,6 +25,7 @@ import {UserBanner} from '../util/UserBanner' import {UserAvatar} from '../util/UserAvatar' import {usePalette} from '../../lib/hooks/usePalette' import {useAnalytics} from '@segment/analytics-react-native' +import {cleanError} from '../../../lib/strings' export const snapPoints = ['80%'] @@ -65,7 +66,7 @@ export function Component({ setNewUserAvatar(finalImg) setUserAvatar(finalImg.path) } catch (e: any) { - setError(e.message || e.toString()) + setError(cleanError(e)) } } const onSelectNewBanner = async (img: PickedImage) => { @@ -75,7 +76,7 @@ export function Component({ setNewUserBanner(finalImg) setUserBanner(finalImg.path) } catch (e: any) { - setError(e.message || e.toString()) + setError(cleanError(e)) } } const onPressSave = async () => { @@ -102,7 +103,7 @@ export function Component({ 'Failed to save your profile. Check your internet connection and try again.', ) } else { - setError(e.message) + setError(cleanError(e)) } } setProcessing(false) diff --git a/src/view/com/modals/ReportAccount.tsx b/src/view/com/modals/ReportAccount.tsx index 1385d57113..91ab4a3a9f 100644 --- a/src/view/com/modals/ReportAccount.tsx +++ b/src/view/com/modals/ReportAccount.tsx @@ -11,6 +11,7 @@ import {s, colors, gradients} from '../../lib/styles' import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup' import {Text} from '../util/text/Text' import {ErrorMessage} from '../util/error/ErrorMessage' +import {cleanError} from '../../../lib/strings' const ITEMS: RadioGroupItem[] = [ {key: 'spam', label: 'Spam or excessive repeat posts'}, @@ -34,7 +35,7 @@ export function Component() { store.shell.closeModal() return } catch (e: any) { - setError(e.toString()) + setError(cleanError(e)) setIsProcessing(false) } } diff --git a/src/view/com/modals/ReportPost.tsx b/src/view/com/modals/ReportPost.tsx index 8a3a1f758c..a051bf2b0d 100644 --- a/src/view/com/modals/ReportPost.tsx +++ b/src/view/com/modals/ReportPost.tsx @@ -11,6 +11,7 @@ import {s, colors, gradients} from '../../lib/styles' import {RadioGroup, RadioGroupItem} from '../util/forms/RadioGroup' import {Text} from '../util/text/Text' import {ErrorMessage} from '../util/error/ErrorMessage' +import {cleanError} from '../../../lib/strings' const ITEMS: RadioGroupItem[] = [ {key: 'spam', label: 'Spam or excessive repeat posts'}, @@ -35,7 +36,7 @@ export function Component() { store.shell.closeModal() return } catch (e: any) { - setError(e.toString()) + setError(cleanError(e)) setIsProcessing(false) } } diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx index 48c8b9a1f9..35012abc05 100644 --- a/src/view/com/notifications/FeedItem.tsx +++ b/src/view/com/notifications/FeedItem.tsx @@ -18,7 +18,6 @@ import {HeartIconSolid} from '../../lib/icons' import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' import {ImageHorzList} from '../util/images/ImageHorzList' -import {ErrorMessage} from '../util/error/ErrorMessage' import {Post} from '../post/Post' import {Link} from '../util/Link' import {usePalette} from '../../lib/hooks/usePalette' @@ -74,6 +73,10 @@ export const FeedItem = observer(function FeedItem({ } if (item.isReply || item.isMention) { + if (item.additionalPost?.error) { + // hide errors - it doesnt help the user to show them + return + } return ( } - if (additionalPost.error) { - return - } const text = additionalPost.thread?.postRecord.text const images = ( additionalPost.thread.post.embed as AppBskyEmbedImages.Presented diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index e549c21142..05cec3f2ae 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -40,9 +40,7 @@ export const Home = observer(function Home({ return } store.log.debug('Polling home feed') - store.me.mainFeed.checkForLatest().catch(e => { - store.log.error('Failed to poll feed', e) - }) + store.me.mainFeed.checkForLatest() }, [appState, visible, store], ) diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx index eade8f6dc3..e4c0d1382a 100644 --- a/src/view/screens/Notifications.tsx +++ b/src/view/screens/Notifications.tsx @@ -22,14 +22,9 @@ export const Notifications = ({navIdx, visible}: ScreenParams) => { return } store.log.debug('Updating notifications feed') - store.me.notifications - .update() - .catch(e => { - store.log.error('Error while updating notifications feed', e) - }) - .then(() => { - store.me.notifications.updateReadState() - }) + store.me.notifications.update().then(() => { + store.me.notifications.updateReadState() + }) store.nav.setTitle(navIdx, 'Notifications') }, [visible, store, navIdx])