mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {View} from 'react-native'
3import {AppBskyFeedGetAuthorFeed, AtUri} from '@atproto/api'
4import {Text} from '../util/text/Text'
5import {Button} from '../util/forms/Button'
6import * as Toast from '../util/Toast'
7import {ErrorMessage} from '../util/error/ErrorMessage'
8import {usePalette} from 'lib/hooks/usePalette'
9import {useNavigation} from '@react-navigation/native'
10import {NavigationProp} from 'lib/routes/types'
11import {logger} from '#/logger'
12import {msg as msgLingui, Trans} from '@lingui/macro'
13import {useLingui} from '@lingui/react'
14import {FeedDescriptor} from '#/state/queries/post-feed'
15import {EmptyState} from '../util/EmptyState'
16import {cleanError} from '#/lib/strings/errors'
17import {useRemoveFeedMutation} from '#/state/queries/preferences'
18import * as Prompt from '#/components/Prompt'
19
20export enum KnownError {
21 Block = 'Block',
22 FeedgenDoesNotExist = 'FeedgenDoesNotExist',
23 FeedgenMisconfigured = 'FeedgenMisconfigured',
24 FeedgenBadResponse = 'FeedgenBadResponse',
25 FeedgenOffline = 'FeedgenOffline',
26 FeedgenUnknown = 'FeedgenUnknown',
27 FeedNSFPublic = 'FeedNSFPublic',
28 FeedTooManyRequests = 'FeedTooManyRequests',
29 Unknown = 'Unknown',
30}
31
32export function FeedErrorMessage({
33 feedDesc,
34 error,
35 onPressTryAgain,
36}: {
37 feedDesc: FeedDescriptor
38 error?: Error
39 onPressTryAgain: () => void
40}) {
41 const {_: _l} = useLingui()
42 const knownError = React.useMemo(
43 () => detectKnownError(feedDesc, error),
44 [feedDesc, error],
45 )
46 if (
47 typeof knownError !== 'undefined' &&
48 knownError !== KnownError.Unknown &&
49 (feedDesc.startsWith('feedgen') || knownError === KnownError.FeedNSFPublic)
50 ) {
51 return (
52 <FeedgenErrorMessage
53 feedDesc={feedDesc}
54 knownError={knownError}
55 rawError={error}
56 />
57 )
58 }
59
60 if (knownError === KnownError.Block) {
61 return (
62 <EmptyState
63 icon="ban"
64 message={_l(msgLingui`Posts hidden`)}
65 style={{paddingVertical: 40}}
66 />
67 )
68 }
69
70 return (
71 <ErrorMessage
72 message={cleanError(error)}
73 onPressTryAgain={onPressTryAgain}
74 />
75 )
76}
77
78function FeedgenErrorMessage({
79 feedDesc,
80 knownError,
81 rawError,
82}: {
83 feedDesc: FeedDescriptor
84 knownError: KnownError
85 rawError?: Error
86}) {
87 const pal = usePalette('default')
88 const {_: _l} = useLingui()
89 const navigation = useNavigation<NavigationProp>()
90 const msg = React.useMemo(
91 () =>
92 ({
93 [KnownError.Unknown]: '',
94 [KnownError.Block]: '',
95 [KnownError.FeedgenDoesNotExist]: _l(
96 msgLingui`Hmm, we're having trouble finding this feed. It may have been deleted.`,
97 ),
98 [KnownError.FeedgenMisconfigured]: _l(
99 msgLingui`Hmm, the feed server appears to be misconfigured. Please let the feed owner know about this issue.`,
100 ),
101 [KnownError.FeedgenBadResponse]: _l(
102 msgLingui`Hmm, the feed server gave a bad response. Please let the feed owner know about this issue.`,
103 ),
104 [KnownError.FeedgenOffline]: _l(
105 msgLingui`Hmm, the feed server appears to be offline. Please let the feed owner know about this issue.`,
106 ),
107 [KnownError.FeedNSFPublic]: _l(
108 msgLingui`This content is not viewable without a Bluesky account.`,
109 ),
110 [KnownError.FeedgenUnknown]: _l(
111 msgLingui`Hmm, some kind of issue occurred when contacting the feed server. Please let the feed owner know about this issue.`,
112 ),
113 [KnownError.FeedTooManyRequests]: _l(
114 msgLingui`This feed is currently receiving high traffic and is temporarily unavailable. Please try again later.`,
115 ),
116 }[knownError]),
117 [_l, knownError],
118 )
119 const [_, uri] = feedDesc.split('|')
120 const [ownerDid] = safeParseFeedgenUri(uri)
121 const removePromptControl = Prompt.usePromptControl()
122 const {mutateAsync: removeFeed} = useRemoveFeedMutation()
123
124 const onViewProfile = React.useCallback(() => {
125 navigation.navigate('Profile', {name: ownerDid})
126 }, [navigation, ownerDid])
127
128 const onPressRemoveFeed = React.useCallback(() => {
129 removePromptControl.open()
130 }, [removePromptControl])
131
132 const onRemoveFeed = React.useCallback(async () => {
133 try {
134 await removeFeed({uri})
135 } catch (err) {
136 Toast.show(
137 _l(
138 msgLingui`There was an an issue removing this feed. Please check your internet connection and try again.`,
139 ),
140 )
141 logger.error('Failed to remove feed', {message: err})
142 }
143 }, [uri, removeFeed, _l])
144
145 const cta = React.useMemo(() => {
146 switch (knownError) {
147 case KnownError.FeedNSFPublic: {
148 return null
149 }
150 case KnownError.FeedgenDoesNotExist:
151 case KnownError.FeedgenMisconfigured:
152 case KnownError.FeedgenBadResponse:
153 case KnownError.FeedgenOffline:
154 case KnownError.FeedgenUnknown: {
155 return (
156 <View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
157 {knownError === KnownError.FeedgenDoesNotExist && (
158 <Button
159 type="inverted"
160 label={_l(msgLingui`Remove feed`)}
161 onPress={onRemoveFeed}
162 />
163 )}
164 <Button
165 type="default-light"
166 label={_l(msgLingui`View profile`)}
167 onPress={onViewProfile}
168 />
169 </View>
170 )
171 }
172 }
173 }, [knownError, onViewProfile, onRemoveFeed, _l])
174
175 return (
176 <>
177 <View
178 style={[
179 pal.border,
180 pal.viewLight,
181 {
182 borderTopWidth: 1,
183 paddingHorizontal: 20,
184 paddingVertical: 18,
185 gap: 12,
186 },
187 ]}>
188 <Text style={pal.text}>{msg}</Text>
189
190 {rawError?.message && (
191 <Text style={pal.textLight}>
192 <Trans>Message from server: {rawError.message}</Trans>
193 </Text>
194 )}
195
196 {cta}
197 </View>
198
199 <Prompt.Basic
200 control={removePromptControl}
201 title={_l(msgLingui`Remove feed?`)}
202 description={_l(msgLingui`Remove this feed from your saved feeds`)}
203 onConfirm={onPressRemoveFeed}
204 confirmButtonCta={_l(msgLingui`Remove`)}
205 confirmButtonColor="negative"
206 />
207 </>
208 )
209}
210
211function safeParseFeedgenUri(uri: string): [string, string] {
212 try {
213 const urip = new AtUri(uri)
214 return [urip.hostname, urip.rkey]
215 } catch {
216 return ['', '']
217 }
218}
219
220function detectKnownError(
221 feedDesc: FeedDescriptor,
222 error: any,
223): KnownError | undefined {
224 if (!error) {
225 return undefined
226 }
227 if (
228 error instanceof AppBskyFeedGetAuthorFeed.BlockedActorError ||
229 error instanceof AppBskyFeedGetAuthorFeed.BlockedByActorError
230 ) {
231 return KnownError.Block
232 }
233
234 // check status codes
235 if (error?.status === 429) {
236 return KnownError.FeedTooManyRequests
237 }
238
239 // convert error to string and continue
240 if (typeof error !== 'string') {
241 error = error.toString()
242 }
243 if (error.includes(KnownError.FeedNSFPublic)) {
244 return KnownError.FeedNSFPublic
245 }
246 if (!feedDesc.startsWith('feedgen')) {
247 return KnownError.Unknown
248 }
249 if (error.includes('could not find feed')) {
250 return KnownError.FeedgenDoesNotExist
251 }
252 if (error.includes('feed unavailable')) {
253 return KnownError.FeedgenOffline
254 }
255 if (error.includes('invalid did document')) {
256 return KnownError.FeedgenMisconfigured
257 }
258 if (error.includes('could not resolve did document')) {
259 return KnownError.FeedgenMisconfigured
260 }
261 if (
262 error.includes('invalid feed generator service details in did document')
263 ) {
264 return KnownError.FeedgenMisconfigured
265 }
266 if (error.includes('feed provided an invalid response')) {
267 return KnownError.FeedgenBadResponse
268 }
269 return KnownError.FeedgenUnknown
270}