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