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