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 {useModalControls} from '#/state/modals'
13import {msg as msgLingui, Trans} from '@lingui/macro'
14import {useLingui} from '@lingui/react'
15import {FeedDescriptor} from '#/state/queries/post-feed'
16import {EmptyState} from '../util/EmptyState'
17import {cleanError} from '#/lib/strings/errors'
18import {useRemoveFeedMutation} from '#/state/queries/preferences'
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')
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 {openModal, closeModal} = useModalControls()
122 const {mutateAsync: removeFeed} = useRemoveFeedMutation()
123
124 const onViewProfile = React.useCallback(() => {
125 navigation.navigate('Profile', {name: ownerDid})
126 }, [navigation, ownerDid])
127
128 const onRemoveFeed = React.useCallback(async () => {
129 openModal({
130 name: 'confirm',
131 title: _l(msgLingui`Remove feed`),
132 message: _l(msgLingui`Remove this feed from your saved feeds?`),
133 async onPressConfirm() {
134 try {
135 await removeFeed({uri})
136 } catch (err) {
137 Toast.show(
138 _l(
139 msgLingui`There was an an issue removing this feed. Please check your internet connection and try again.`,
140 ),
141 )
142 logger.error('Failed to remove feed', {message: err})
143 }
144 },
145 onPressCancel() {
146 closeModal()
147 },
148 })
149 }, [openModal, closeModal, uri, removeFeed, _l])
150
151 const cta = React.useMemo(() => {
152 switch (knownError) {
153 case KnownError.FeedNSFPublic: {
154 return null
155 }
156 case KnownError.FeedgenDoesNotExist:
157 case KnownError.FeedgenMisconfigured:
158 case KnownError.FeedgenBadResponse:
159 case KnownError.FeedgenOffline:
160 case KnownError.FeedgenUnknown: {
161 return (
162 <View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}>
163 {knownError === KnownError.FeedgenDoesNotExist && (
164 <Button
165 type="inverted"
166 label={_l(msgLingui`Remove feed`)}
167 onPress={onRemoveFeed}
168 />
169 )}
170 <Button
171 type="default-light"
172 label={_l(msgLingui`View profile`)}
173 onPress={onViewProfile}
174 />
175 </View>
176 )
177 }
178 }
179 }, [knownError, onViewProfile, onRemoveFeed, _l])
180
181 return (
182 <View
183 style={[
184 pal.border,
185 pal.viewLight,
186 {
187 borderTopWidth: 1,
188 paddingHorizontal: 20,
189 paddingVertical: 18,
190 gap: 12,
191 },
192 ]}>
193 <Text style={pal.text}>{msg}</Text>
194
195 {rawError?.message && (
196 <Text style={pal.textLight}>
197 <Trans>Message from server: {rawError.message}</Trans>
198 </Text>
199 )}
200
201 {cta}
202 </View>
203 )
204}
205
206function safeParseFeedgenUri(uri: string): [string, string] {
207 try {
208 const urip = new AtUri(uri)
209 return [urip.hostname, urip.rkey]
210 } catch {
211 return ['', '']
212 }
213}
214
215function detectKnownError(
216 feedDesc: FeedDescriptor,
217 error: any,
218): KnownError | undefined {
219 if (!error) {
220 return undefined
221 }
222 if (
223 error instanceof AppBskyFeedGetAuthorFeed.BlockedActorError ||
224 error instanceof AppBskyFeedGetAuthorFeed.BlockedByActorError
225 ) {
226 return KnownError.Block
227 }
228
229 // check status codes
230 if (error?.status === 429) {
231 return KnownError.FeedTooManyRequests
232 }
233
234 // convert error to string and continue
235 if (typeof error !== 'string') {
236 error = error.toString()
237 }
238 if (!feedDesc.startsWith('feedgen')) {
239 return KnownError.Unknown
240 }
241 if (error.includes('could not find feed')) {
242 return KnownError.FeedgenDoesNotExist
243 }
244 if (error.includes('feed unavailable')) {
245 return KnownError.FeedgenOffline
246 }
247 if (error.includes('invalid did document')) {
248 return KnownError.FeedgenMisconfigured
249 }
250 if (error.includes('could not resolve did document')) {
251 return KnownError.FeedgenMisconfigured
252 }
253 if (
254 error.includes('invalid feed generator service details in did document')
255 ) {
256 return KnownError.FeedgenMisconfigured
257 }
258 if (error.includes('feed provided an invalid response')) {
259 return KnownError.FeedgenBadResponse
260 }
261 if (error.includes(KnownError.FeedNSFPublic)) {
262 return KnownError.FeedNSFPublic
263 }
264 return KnownError.FeedgenUnknown
265}