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