Bluesky app fork with some witchin' additions 馃挮
at main 287 lines 8.3 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} 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}