mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
at tooltip 8.2 kB view raw
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}