import React from 'react'
import {View} from 'react-native'
import {AppBskyActorDefs, AppBskyFeedGetAuthorFeed, AtUri} from '@atproto/api'
import {msg as msgLingui, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native'
import {cleanError} from '#/lib/strings/errors'
import {logger} from '#/logger'
import {FeedDescriptor} from '#/state/queries/post-feed'
import {useRemoveFeedMutation} from '#/state/queries/preferences'
import {usePalette} from 'lib/hooks/usePalette'
import {NavigationProp} from 'lib/routes/types'
import * as Prompt from '#/components/Prompt'
import {EmptyState} from '../util/EmptyState'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {Button} from '../util/forms/Button'
import {Text} from '../util/text/Text'
import * as Toast from '../util/Toast'
export enum KnownError {
Block = 'Block',
FeedgenDoesNotExist = 'FeedgenDoesNotExist',
FeedgenMisconfigured = 'FeedgenMisconfigured',
FeedgenBadResponse = 'FeedgenBadResponse',
FeedgenOffline = 'FeedgenOffline',
FeedgenUnknown = 'FeedgenUnknown',
FeedNSFPublic = 'FeedNSFPublic',
FeedTooManyRequests = 'FeedTooManyRequests',
Unknown = 'Unknown',
}
export function FeedErrorMessage({
feedDesc,
error,
onPressTryAgain,
savedFeedConfig,
}: {
feedDesc: FeedDescriptor
error?: Error
onPressTryAgain: () => void
savedFeedConfig?: AppBskyActorDefs.SavedFeed
}) {
const {_: _l} = useLingui()
const knownError = React.useMemo(
() => detectKnownError(feedDesc, error),
[feedDesc, error],
)
if (
typeof knownError !== 'undefined' &&
knownError !== KnownError.Unknown &&
feedDesc.startsWith('feedgen')
) {
return (
)
}
if (knownError === KnownError.Block) {
return (
)
}
return (
)
}
function FeedgenErrorMessage({
feedDesc,
knownError,
rawError,
savedFeedConfig,
}: {
feedDesc: FeedDescriptor
knownError: KnownError
rawError?: Error
savedFeedConfig?: AppBskyActorDefs.SavedFeed
}) {
const pal = usePalette('default')
const {_: _l} = useLingui()
const navigation = useNavigation()
const msg = React.useMemo(
() =>
({
[KnownError.Unknown]: '',
[KnownError.Block]: '',
[KnownError.FeedgenDoesNotExist]: _l(
msgLingui`Hmm, we're having trouble finding this feed. It may have been deleted.`,
),
[KnownError.FeedgenMisconfigured]: _l(
msgLingui`Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue.`,
),
[KnownError.FeedgenBadResponse]: _l(
msgLingui`Hmm, the feed server gave a bad response. Please let the feed owner know about this issue.`,
),
[KnownError.FeedgenOffline]: _l(
msgLingui`Hmm, the feed server appears to be offline. Please let the feed owner know about this issue.`,
),
[KnownError.FeedNSFPublic]: _l(
msgLingui`This content is not viewable without a Bluesky account.`,
),
[KnownError.FeedgenUnknown]: _l(
msgLingui`Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue.`,
),
[KnownError.FeedTooManyRequests]: _l(
msgLingui`This feed is currently receiving high traffic and is temporarily unavailable. Please try again later.`,
),
}[knownError]),
[_l, knownError],
)
const [_, uri] = feedDesc.split('|')
const [ownerDid] = safeParseFeedgenUri(uri)
const removePromptControl = Prompt.usePromptControl()
const {mutateAsync: removeFeed} = useRemoveFeedMutation()
const onViewProfile = React.useCallback(() => {
navigation.navigate('Profile', {name: ownerDid})
}, [navigation, ownerDid])
const onPressRemoveFeed = React.useCallback(() => {
removePromptControl.open()
}, [removePromptControl])
const onRemoveFeed = React.useCallback(async () => {
try {
if (!savedFeedConfig) return
await removeFeed(savedFeedConfig)
} catch (err) {
Toast.show(
_l(
msgLingui`There was an an issue removing this feed. Please check your internet connection and try again.`,
),
'exclamation-circle',
)
logger.error('Failed to remove feed', {message: err})
}
}, [removeFeed, _l, savedFeedConfig])
const cta = React.useMemo(() => {
switch (knownError) {
case KnownError.FeedNSFPublic: {
return null
}
case KnownError.FeedgenDoesNotExist:
case KnownError.FeedgenMisconfigured:
case KnownError.FeedgenBadResponse:
case KnownError.FeedgenOffline:
case KnownError.FeedgenUnknown: {
return (
{knownError === KnownError.FeedgenDoesNotExist &&
savedFeedConfig && (
)}
)
}
}
}, [knownError, onViewProfile, onRemoveFeed, _l, savedFeedConfig])
return (
<>
{msg}
{rawError?.message && (
Message from server: {rawError.message}
)}
{cta}
>
)
}
function safeParseFeedgenUri(uri: string): [string, string] {
try {
const urip = new AtUri(uri)
return [urip.hostname, urip.rkey]
} catch {
return ['', '']
}
}
function detectKnownError(
feedDesc: FeedDescriptor,
error: any,
): KnownError | undefined {
if (!error) {
return undefined
}
if (
error instanceof AppBskyFeedGetAuthorFeed.BlockedActorError ||
error instanceof AppBskyFeedGetAuthorFeed.BlockedByActorError
) {
return KnownError.Block
}
// check status codes
if (error?.status === 429) {
return KnownError.FeedTooManyRequests
}
// convert error to string and continue
if (typeof error !== 'string') {
error = error.toString()
}
if (error.includes(KnownError.FeedNSFPublic)) {
return KnownError.FeedNSFPublic
}
if (!feedDesc.startsWith('feedgen')) {
return KnownError.Unknown
}
if (error.includes('could not find feed')) {
return KnownError.FeedgenDoesNotExist
}
if (error.includes('feed unavailable')) {
return KnownError.FeedgenOffline
}
if (error.includes('invalid did document')) {
return KnownError.FeedgenMisconfigured
}
if (error.includes('could not resolve did document')) {
return KnownError.FeedgenMisconfigured
}
if (
error.includes('invalid feed generator service details in did document')
) {
return KnownError.FeedgenMisconfigured
}
if (error.includes('invalid response')) {
return KnownError.FeedgenBadResponse
}
return KnownError.FeedgenUnknown
}