tangled mirror of catsky-🐱 Soothing soft social-app fork with all the niche toggles! (Unofficial); for issues and PRs please put them on github:NekoDrone/catsky-social

chore: sync with upstream (#55)

* Send inferrable interactions to third-party feeds (#9094)

* Fix link crash (#9102)

* fix link crash

* fix link crash

* Log OTA errors properly (#9101)

* Log OTA errors properly

* filter out network errors

* don't send some "activity no longer available" errors (#9100)

* remove root sibling library (#9097)

* Catch errors on geolocation request, reduce Sentry logs (#9098)

---------

Co-authored-by: dan <dan.abramov@gmail.com>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

+8 -12
jest/test-utils.tsx
··· 1 - import React from 'react' 2 - import {render} from '@testing-library/react-native' 3 1 import {GestureHandlerRootView} from 'react-native-gesture-handler' 4 - import {RootSiblingParent} from 'react-native-root-siblings' 5 2 import {SafeAreaProvider} from 'react-native-safe-area-context' 6 - import {RootStoreProvider, RootStoreModel} from '../src/state' 3 + import {render} from '@testing-library/react-native' 4 + 7 5 import {ThemeProvider} from '../src/lib/ThemeContext' 6 + import {type RootStoreModel, RootStoreProvider} from '../src/state' 8 7 9 8 const customRender = (ui: any, rootStore: RootStoreModel) => 10 9 render( 11 - // eslint-disable-next-line react-native/no-inline-styles 12 10 <GestureHandlerRootView style={{flex: 1}}> 13 - <RootSiblingParent> 14 - <RootStoreProvider value={rootStore}> 15 - <ThemeProvider theme="light"> 16 - <SafeAreaProvider>{ui}</SafeAreaProvider> 17 - </ThemeProvider> 18 - </RootStoreProvider> 19 - </RootSiblingParent> 11 + <RootStoreProvider value={rootStore}> 12 + <ThemeProvider theme="light"> 13 + <SafeAreaProvider>{ui}</SafeAreaProvider> 14 + </ThemeProvider> 15 + </RootStoreProvider> 20 16 </GestureHandlerRootView>, 21 17 ) 22 18
-1
package.json
··· 196 196 "react-native-progress": "bluesky-social/react-native-progress", 197 197 "react-native-qrcode-styled": "^0.3.3", 198 198 "react-native-reanimated": "^3.19.1", 199 - "react-native-root-siblings": "^5.0.1", 200 199 "react-native-safe-area-context": "~5.6.0", 201 200 "react-native-screens": "~4.16.0", 202 201 "react-native-svg": "15.12.1",
+61 -60
src/App.native.tsx
··· 4 4 5 5 import React, {useEffect, useState} from 'react' 6 6 import {GestureHandlerRootView} from 'react-native-gesture-handler' 7 - import {RootSiblingParent} from 'react-native-root-siblings' 8 7 import { 9 8 initialWindowMetrics, 10 9 SafeAreaProvider, ··· 84 83 } 85 84 if (isAndroid) { 86 85 // iOS is handled by the config plugin -sfn 87 - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP) 86 + ScreenOrientation.lockAsync( 87 + ScreenOrientation.OrientationLock.PORTRAIT_UP, 88 + ).catch(error => 89 + logger.debug('Could not lock orientation', {safeMessage: error}), 90 + ) 88 91 } 89 92 90 93 /** ··· 133 136 <ThemeProvider theme={theme}> 134 137 <ContextMenuProvider> 135 138 <Splash isReady={isReady && hasCheckedReferrer}> 136 - <RootSiblingParent> 137 - <VideoVolumeProvider> 138 - <React.Fragment 139 - // Resets the entire tree below when it changes: 140 - key={currentAccount?.did}> 141 - <QueryProvider currentDid={currentAccount?.did}> 142 - <PolicyUpdateOverlayProvider> 143 - <StatsigProvider> 144 - <AgeAssuranceProvider> 145 - <ComposerProvider> 146 - <MessagesProvider> 147 - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 148 - <LabelDefsProvider> 149 - <ModerationOptsProvider> 150 - <LoggedOutViewProvider> 151 - <SelectedFeedProvider> 152 - <HiddenRepliesProvider> 153 - <HomeBadgeProvider> 154 - <UnreadNotifsProvider> 155 - <BackgroundNotificationPreferencesProvider> 156 - <MutedThreadsProvider> 157 - <ProgressGuideProvider> 158 - <ServiceAccountManager> 159 - <EmailVerificationProvider> 160 - <HideBottomBarBorderProvider> 161 - <GestureHandlerRootView 162 - style={s.h100pct}> 163 - <GlobalGestureEventsProvider> 164 - <IntentDialogProvider> 165 - <TestCtrls /> 166 - <Shell /> 167 - <NuxDialogs /> 168 - <ToastOutlet /> 169 - </IntentDialogProvider> 170 - </GlobalGestureEventsProvider> 171 - </GestureHandlerRootView> 172 - </HideBottomBarBorderProvider> 173 - </EmailVerificationProvider> 174 - </ServiceAccountManager> 175 - </ProgressGuideProvider> 176 - </MutedThreadsProvider> 177 - </BackgroundNotificationPreferencesProvider> 178 - </UnreadNotifsProvider> 179 - </HomeBadgeProvider> 180 - </HiddenRepliesProvider> 181 - </SelectedFeedProvider> 182 - </LoggedOutViewProvider> 183 - </ModerationOptsProvider> 184 - </LabelDefsProvider> 185 - </MessagesProvider> 186 - </ComposerProvider> 187 - </AgeAssuranceProvider> 188 - </StatsigProvider> 189 - </PolicyUpdateOverlayProvider> 190 - </QueryProvider> 191 - </React.Fragment> 192 - </VideoVolumeProvider> 193 - </RootSiblingParent> 139 + <VideoVolumeProvider> 140 + <React.Fragment 141 + // Resets the entire tree below when it changes: 142 + key={currentAccount?.did}> 143 + <QueryProvider currentDid={currentAccount?.did}> 144 + <PolicyUpdateOverlayProvider> 145 + <StatsigProvider> 146 + <AgeAssuranceProvider> 147 + <ComposerProvider> 148 + <MessagesProvider> 149 + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 150 + <LabelDefsProvider> 151 + <ModerationOptsProvider> 152 + <LoggedOutViewProvider> 153 + <SelectedFeedProvider> 154 + <HiddenRepliesProvider> 155 + <HomeBadgeProvider> 156 + <UnreadNotifsProvider> 157 + <BackgroundNotificationPreferencesProvider> 158 + <MutedThreadsProvider> 159 + <ProgressGuideProvider> 160 + <ServiceAccountManager> 161 + <EmailVerificationProvider> 162 + <HideBottomBarBorderProvider> 163 + <GestureHandlerRootView 164 + style={s.h100pct}> 165 + <GlobalGestureEventsProvider> 166 + <IntentDialogProvider> 167 + <TestCtrls /> 168 + <Shell /> 169 + <NuxDialogs /> 170 + <ToastOutlet /> 171 + </IntentDialogProvider> 172 + </GlobalGestureEventsProvider> 173 + </GestureHandlerRootView> 174 + </HideBottomBarBorderProvider> 175 + </EmailVerificationProvider> 176 + </ServiceAccountManager> 177 + </ProgressGuideProvider> 178 + </MutedThreadsProvider> 179 + </BackgroundNotificationPreferencesProvider> 180 + </UnreadNotifsProvider> 181 + </HomeBadgeProvider> 182 + </HiddenRepliesProvider> 183 + </SelectedFeedProvider> 184 + </LoggedOutViewProvider> 185 + </ModerationOptsProvider> 186 + </LabelDefsProvider> 187 + </MessagesProvider> 188 + </ComposerProvider> 189 + </AgeAssuranceProvider> 190 + </StatsigProvider> 191 + </PolicyUpdateOverlayProvider> 192 + </QueryProvider> 193 + </React.Fragment> 194 + </VideoVolumeProvider> 194 195 </Splash> 195 196 </ContextMenuProvider> 196 197 </ThemeProvider>
+54 -57
src/App.web.tsx
··· 3 3 import './style.css' 4 4 5 5 import React, {useEffect, useState} from 'react' 6 - import {RootSiblingParent} from 'react-native-root-siblings' 7 6 import {SafeAreaProvider} from 'react-native-safe-area-context' 8 7 import {msg} from '@lingui/macro' 9 8 import {useLingui} from '@lingui/react' ··· 111 110 <Alf theme={theme}> 112 111 <ThemeProvider theme={theme}> 113 112 <ContextMenuProvider> 114 - <RootSiblingParent> 115 - <VideoVolumeProvider> 116 - <ActiveVideoProvider> 117 - <React.Fragment 118 - // Resets the entire tree below when it changes: 119 - key={currentAccount?.did}> 120 - <QueryProvider currentDid={currentAccount?.did}> 121 - <PolicyUpdateOverlayProvider> 122 - <StatsigProvider> 123 - <AgeAssuranceProvider> 124 - <ComposerProvider> 125 - <MessagesProvider> 126 - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 127 - <LabelDefsProvider> 128 - <ModerationOptsProvider> 129 - <LoggedOutViewProvider> 130 - <SelectedFeedProvider> 131 - <HiddenRepliesProvider> 132 - <HomeBadgeProvider> 133 - <UnreadNotifsProvider> 134 - <BackgroundNotificationPreferencesProvider> 135 - <MutedThreadsProvider> 136 - <SafeAreaProvider> 137 - <ProgressGuideProvider> 138 - <ServiceConfigProvider> 139 - <EmailVerificationProvider> 140 - <HideBottomBarBorderProvider> 141 - <IntentDialogProvider> 142 - <Shell /> 143 - <NuxDialogs /> 144 - <ToastOutlet /> 145 - </IntentDialogProvider> 146 - </HideBottomBarBorderProvider> 147 - </EmailVerificationProvider> 148 - </ServiceConfigProvider> 149 - </ProgressGuideProvider> 150 - </SafeAreaProvider> 151 - </MutedThreadsProvider> 152 - </BackgroundNotificationPreferencesProvider> 153 - </UnreadNotifsProvider> 154 - </HomeBadgeProvider> 155 - </HiddenRepliesProvider> 156 - </SelectedFeedProvider> 157 - </LoggedOutViewProvider> 158 - </ModerationOptsProvider> 159 - </LabelDefsProvider> 160 - </MessagesProvider> 161 - </ComposerProvider> 162 - </AgeAssuranceProvider> 163 - </StatsigProvider> 164 - </PolicyUpdateOverlayProvider> 165 - </QueryProvider> 166 - </React.Fragment> 167 - </ActiveVideoProvider> 168 - </VideoVolumeProvider> 169 - </RootSiblingParent> 113 + <VideoVolumeProvider> 114 + <ActiveVideoProvider> 115 + <React.Fragment 116 + // Resets the entire tree below when it changes: 117 + key={currentAccount?.did}> 118 + <QueryProvider currentDid={currentAccount?.did}> 119 + <PolicyUpdateOverlayProvider> 120 + <StatsigProvider> 121 + <AgeAssuranceProvider> 122 + <ComposerProvider> 123 + <MessagesProvider> 124 + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 125 + <LabelDefsProvider> 126 + <ModerationOptsProvider> 127 + <LoggedOutViewProvider> 128 + <SelectedFeedProvider> 129 + <HiddenRepliesProvider> 130 + <HomeBadgeProvider> 131 + <UnreadNotifsProvider> 132 + <BackgroundNotificationPreferencesProvider> 133 + <MutedThreadsProvider> 134 + <SafeAreaProvider> 135 + <ProgressGuideProvider> 136 + <ServiceConfigProvider> 137 + <EmailVerificationProvider> 138 + <HideBottomBarBorderProvider> 139 + <IntentDialogProvider> 140 + <Shell /> 141 + <NuxDialogs /> 142 + <ToastOutlet /> 143 + </IntentDialogProvider> 144 + </HideBottomBarBorderProvider> 145 + </EmailVerificationProvider> 146 + </ServiceConfigProvider> 147 + </ProgressGuideProvider> 148 + </SafeAreaProvider> 149 + </MutedThreadsProvider> 150 + </BackgroundNotificationPreferencesProvider> 151 + </UnreadNotifsProvider> 152 + </HomeBadgeProvider> 153 + </HiddenRepliesProvider> 154 + </SelectedFeedProvider> 155 + </LoggedOutViewProvider> 156 + </ModerationOptsProvider> 157 + </LabelDefsProvider> 158 + </MessagesProvider> 159 + </ComposerProvider> 160 + </AgeAssuranceProvider> 161 + </StatsigProvider> 162 + </PolicyUpdateOverlayProvider> 163 + </QueryProvider> 164 + </React.Fragment> 165 + </ActiveVideoProvider> 166 + </VideoVolumeProvider> 170 167 </ContextMenuProvider> 171 168 </ThemeProvider> 172 169 </Alf>
+10 -4
src/alf/util/systemUI.ts
··· 1 1 import * as SystemUI from 'expo-system-ui' 2 2 3 + import {logger} from '#/logger' 3 4 import {isAndroid} from '#/platform/detection' 4 5 import {type Theme} from '../types' 5 6 6 7 export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { 7 8 if (isAndroid) { 8 - if (themeType === 'theme') { 9 - SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 10 - } else { 11 - SystemUI.setBackgroundColorAsync('black') 9 + try { 10 + if (themeType === 'theme') { 11 + SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 12 + } else { 13 + SystemUI.setBackgroundColorAsync('black') 14 + } 15 + } catch (error) { 16 + // Can reject with 'The current activity is no longer available' - no big deal 17 + logger.debug('Could not set system UI theme', {safeMessage: error}) 12 18 } 13 19 } 14 20 }
+2 -2
src/components/Link.tsx
··· 165 165 if (isNative && screen !== 'NotFound') { 166 166 const state = navigation.getState() 167 167 // if screen is not in the current navigator, it means it's 168 - // most likely a tab screen 169 - if (!state.routeNames.includes(screen)) { 168 + // most likely a tab screen. note: state can be undefined 169 + if (!state?.routeNames.includes(screen)) { 170 170 const parent = navigation.getParent() 171 171 if ( 172 172 parent &&
+9 -4
src/lib/hooks/useOTAUpdates.ts
··· 10 10 useUpdates, 11 11 } from 'expo-updates' 12 12 13 + import {isNetworkError} from '#/lib/strings/errors' 13 14 import {logger} from '#/logger' 14 15 import {isIOS} from '#/platform/detection' 15 16 import {IS_TESTFLIGHT} from '#/env' ··· 145 146 } else { 146 147 logger.debug('No update available.') 147 148 } 148 - } catch (e) { 149 - logger.error('OTA Update Error', {error: `${e}`}) 149 + } catch (err) { 150 + if (!isNetworkError(err)) { 151 + logger.error('OTA Update Error', {safeMessage: err}) 152 + } 150 153 } 151 154 }, 10e3) 152 155 }, []) ··· 154 157 const onIsTestFlight = React.useCallback(async () => { 155 158 try { 156 159 await updateTestflight() 157 - } catch (e: any) { 158 - logger.error('Internal OTA Update Error', {error: `${e}`}) 160 + } catch (err: any) { 161 + if (!isNetworkError(err)) { 162 + logger.error('Internal OTA Update Error', {safeMessage: err}) 163 + } 159 164 } 160 165 }, []) 161 166
+15 -3
src/state/feed-feedback.tsx
··· 28 28 29 29 export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS] 30 30 31 - export const DIRECT_FEEDBACK_INTERACTIONS = new Set< 31 + export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set< 32 32 AppBskyFeedDefs.Interaction['event'] 33 - >(['app.bsky.feed.defs#requestLess', 'app.bsky.feed.defs#requestMore']) 33 + >([ 34 + // These are explicit actions and are therefore fine to send. 35 + 'app.bsky.feed.defs#requestLess', 36 + 'app.bsky.feed.defs#requestMore', 37 + // These can be inferred from the firehose and are therefore fine to send. 38 + 'app.bsky.feed.defs#interactionLike', 39 + 'app.bsky.feed.defs#interactionQuote', 40 + 'app.bsky.feed.defs#interactionReply', 41 + 'app.bsky.feed.defs#interactionRepost', 42 + // This can be inferred from pagination requests for everything except the very last page 43 + // so it is fine to send. It is crucial for third party algorithmic feeds to receive these. 44 + 'app.bsky.feed.defs#interactionSeen', 45 + ]) 34 46 35 47 const logger = Logger.create(Logger.Context.FeedFeedback) 36 48 ··· 228 240 return false 229 241 } 230 242 const isDiscover = isDiscoverFeed(feed.feedDescriptor) 231 - return isDiscover ? true : DIRECT_FEEDBACK_INTERACTIONS.has(interaction) 243 + return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction) 232 244 } 233 245 234 246 function toString(interaction: AppBskyFeedDefs.Interaction): string {
+36 -1
src/state/geolocation/useSyncedDeviceGeolocation.ts
··· 1 1 import {useEffect, useRef} from 'react' 2 2 import * as Location from 'expo-location' 3 + import {createPermissionHook} from 'expo-modules-core' 3 4 4 5 import {logger} from '#/state/geolocation/logger' 5 6 import {getDeviceGeolocation} from '#/state/geolocation/util' 6 7 import {device, useStorage} from '#/storage' 7 8 8 9 /** 10 + * Location.useForegroundPermissions on web just errors if the navigator.permissions API is not available. 11 + * We need to catch and ignore it, since it's effectively denied. 12 + * @see https://github.com/expo/expo/blob/72f1562ed9cce5ff6dfe04aa415b71632a3d4b87/packages/expo-location/src/Location.ts#L290-L293 13 + */ 14 + const useForegroundPermissions = createPermissionHook({ 15 + getMethod: () => 16 + Location.getForegroundPermissionsAsync().catch(error => { 17 + logger.debug( 18 + 'useForegroundPermission: error getting location permissions', 19 + {safeMessage: error}, 20 + ) 21 + return { 22 + status: Location.PermissionStatus.DENIED, 23 + granted: false, 24 + canAskAgain: false, 25 + expires: 0, 26 + } 27 + }), 28 + requestMethod: () => 29 + Location.requestForegroundPermissionsAsync().catch(error => { 30 + logger.debug( 31 + 'useForegroundPermission: error requesting location permissions', 32 + {safeMessage: error}, 33 + ) 34 + return { 35 + status: Location.PermissionStatus.DENIED, 36 + granted: false, 37 + canAskAgain: false, 38 + expires: 0, 39 + } 40 + }), 41 + }) 42 + 43 + /** 9 44 * Hook to get and sync the device geolocation from the device GPS and store it 10 45 * using device storage. If permissions are not granted, it will clear any cached 11 46 * storage value. 12 47 */ 13 48 export function useSyncedDeviceGeolocation() { 14 49 const synced = useRef(false) 15 - const [status] = Location.useForegroundPermissions() 50 + const [status] = useForegroundPermissions() 16 51 const [deviceGeolocation, setDeviceGeolocation] = useStorage(device, [ 17 52 'deviceGeolocation', 18 53 ])
-5
yarn.lock
··· 17064 17064 invariant "^2.2.4" 17065 17065 react-native-is-edge-to-edge "1.1.7" 17066 17066 17067 - react-native-root-siblings@^5.0.1: 17068 - version "5.0.1" 17069 - resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-5.0.1.tgz#97e050e5155228f65810fb1c466ff8e769c5272c" 17070 - integrity sha512-Ay3k/fBj6ReUkWX5WNS+oEAcgPLEGOK8n7K/L7D85mf3xvd8rm/b4spsv26E4HlFzluVx5HKbxEt9cl0wQ1u3g== 17071 - 17072 17067 react-native-safe-area-context@~5.6.0: 17073 17068 version "5.6.1" 17074 17069 resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz#cb4d249ef1a6f7e8fd0cfdfa9764838dffda26b6"