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