From 22bb9c599f66857462e7710b55b9a6af2d26fdbd Mon Sep 17 00:00:00 2001 From: dan Date: Mon, 29 Sep 2025 20:07:21 +0100 Subject: [PATCH 1/6] Send inferrable interactions to third-party feeds (#9094) --- src/state/feed-feedback.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/state/feed-feedback.tsx b/src/state/feed-feedback.tsx index 88d9a47d0..e56bdc2bd 100644 --- a/src/state/feed-feedback.tsx +++ b/src/state/feed-feedback.tsx @@ -28,9 +28,21 @@ import {useAgent} from './session' export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS] -export const DIRECT_FEEDBACK_INTERACTIONS = new Set< +export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set< AppBskyFeedDefs.Interaction['event'] ->(['app.bsky.feed.defs#requestLess', 'app.bsky.feed.defs#requestMore']) +>([ + // These are explicit actions and are therefore fine to send. + 'app.bsky.feed.defs#requestLess', + 'app.bsky.feed.defs#requestMore', + // These can be inferred from the firehose and are therefore fine to send. + 'app.bsky.feed.defs#interactionLike', + 'app.bsky.feed.defs#interactionQuote', + 'app.bsky.feed.defs#interactionReply', + 'app.bsky.feed.defs#interactionRepost', + // This can be inferred from pagination requests for everything except the very last page + // so it is fine to send. It is crucial for third party algorithmic feeds to receive these. + 'app.bsky.feed.defs#interactionSeen', +]) const logger = Logger.create(Logger.Context.FeedFeedback) @@ -228,7 +240,7 @@ function isInteractionAllowed( return false } const isDiscover = isDiscoverFeed(feed.feedDescriptor) - return isDiscover ? true : DIRECT_FEEDBACK_INTERACTIONS.has(interaction) + return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction) } function toString(interaction: AppBskyFeedDefs.Interaction): string { -- 2.51.2 From 2b32fff1d023de5aaad1b04cdc5591d023b2005d Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 29 Sep 2025 22:14:13 +0300 Subject: [PATCH 2/6] Fix link crash (#9102) * fix link crash * fix link crash --- src/components/Link.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Link.tsx b/src/components/Link.tsx index 421a7fe9d..2b533b599 100644 --- a/src/components/Link.tsx +++ b/src/components/Link.tsx @@ -165,8 +165,8 @@ export function useLink({ if (isNative && screen !== 'NotFound') { const state = navigation.getState() // if screen is not in the current navigator, it means it's - // most likely a tab screen - if (!state.routeNames.includes(screen)) { + // most likely a tab screen. note: state can be undefined + if (!state?.routeNames.includes(screen)) { const parent = navigation.getParent() if ( parent && -- 2.51.2 From 02a25d2aa257ba4b2709c695c5ac9e509143335c Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 29 Sep 2025 22:14:39 +0300 Subject: [PATCH 3/6] Log OTA errors properly (#9101) * Log OTA errors properly * filter out network errors --- src/lib/hooks/useOTAUpdates.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/lib/hooks/useOTAUpdates.ts b/src/lib/hooks/useOTAUpdates.ts index 9c5750606..466d03208 100644 --- a/src/lib/hooks/useOTAUpdates.ts +++ b/src/lib/hooks/useOTAUpdates.ts @@ -10,6 +10,7 @@ import { useUpdates, } from 'expo-updates' +import {isNetworkError} from '#/lib/strings/errors' import {logger} from '#/logger' import {isIOS} from '#/platform/detection' import {IS_TESTFLIGHT} from '#/env' @@ -145,8 +146,10 @@ export function useOTAUpdates() { } else { logger.debug('No update available.') } - } catch (e) { - logger.error('OTA Update Error', {error: `${e}`}) + } catch (err) { + if (!isNetworkError(err)) { + logger.error('OTA Update Error', {safeMessage: err}) + } } }, 10e3) }, []) @@ -154,8 +157,10 @@ export function useOTAUpdates() { const onIsTestFlight = React.useCallback(async () => { try { await updateTestflight() - } catch (e: any) { - logger.error('Internal OTA Update Error', {error: `${e}`}) + } catch (err: any) { + if (!isNetworkError(err)) { + logger.error('Internal OTA Update Error', {safeMessage: err}) + } } }, []) -- 2.51.2 From ae2c9a832ff88a144edc332b1fe4da90ffa2c586 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 29 Sep 2025 22:15:38 +0300 Subject: [PATCH 4/6] don't send some "activity no longer available" errors (#9100) --- src/App.native.tsx | 6 +++++- src/alf/util/systemUI.ts | 14 ++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/App.native.tsx b/src/App.native.tsx index 104a7ecae..9821aa871 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -84,7 +84,11 @@ if (isIOS) { } if (isAndroid) { // iOS is handled by the config plugin -sfn - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP) + ScreenOrientation.lockAsync( + ScreenOrientation.OrientationLock.PORTRAIT_UP, + ).catch(error => + logger.debug('Could not lock orientation', {safeMessage: error}), + ) } /** diff --git a/src/alf/util/systemUI.ts b/src/alf/util/systemUI.ts index 02b5363ff..d013f0c3f 100644 --- a/src/alf/util/systemUI.ts +++ b/src/alf/util/systemUI.ts @@ -1,14 +1,20 @@ import * as SystemUI from 'expo-system-ui' import {type Theme} from '@bsky.app/alf' +import {logger} from '#/logger' import {isAndroid} from '#/platform/detection' export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { if (isAndroid) { - if (themeType === 'theme') { - SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) - } else { - SystemUI.setBackgroundColorAsync('black') + try { + if (themeType === 'theme') { + SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) + } else { + SystemUI.setBackgroundColorAsync('black') + } + } catch (error) { + // Can reject with 'The current activity is no longer available' - no big deal + logger.debug('Could not set system UI theme', {safeMessage: error}) } } } -- 2.51.2 From 031fa95715c7244d744424d93446b5d4d5483697 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 29 Sep 2025 22:31:55 +0300 Subject: [PATCH 5/6] remove root sibling library (#9097) --- jest/test-utils.tsx | 20 +++----- package.json | 1 - src/App.native.tsx | 115 +++++++++++++++++++++----------------------- src/App.web.tsx | 111 +++++++++++++++++++++--------------------- yarn.lock | 5 -- 5 files changed, 118 insertions(+), 134 deletions(-) diff --git a/jest/test-utils.tsx b/jest/test-utils.tsx index 0a22d792b..264b31fae 100644 --- a/jest/test-utils.tsx +++ b/jest/test-utils.tsx @@ -1,22 +1,18 @@ -import React from 'react' -import {render} from '@testing-library/react-native' import {GestureHandlerRootView} from 'react-native-gesture-handler' -import {RootSiblingParent} from 'react-native-root-siblings' import {SafeAreaProvider} from 'react-native-safe-area-context' -import {RootStoreProvider, RootStoreModel} from '../src/state' +import {render} from '@testing-library/react-native' + import {ThemeProvider} from '../src/lib/ThemeContext' +import {type RootStoreModel, RootStoreProvider} from '../src/state' const customRender = (ui: any, rootStore: RootStoreModel) => render( - // eslint-disable-next-line react-native/no-inline-styles - - - - {ui} - - - + + + {ui} + + , ) diff --git a/package.json b/package.json index 71c28fe0a..6ff871110 100644 --- a/package.json +++ b/package.json @@ -197,7 +197,6 @@ "react-native-progress": "bluesky-social/react-native-progress", "react-native-qrcode-styled": "^0.3.3", "react-native-reanimated": "^3.19.1", - "react-native-root-siblings": "^5.0.1", "react-native-safe-area-context": "~5.6.0", "react-native-screens": "~4.16.0", "react-native-svg": "15.12.1", diff --git a/src/App.native.tsx b/src/App.native.tsx index 9821aa871..30a5e8129 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -4,7 +4,6 @@ import '#/view/icons' import React, {useEffect, useState} from 'react' import {GestureHandlerRootView} from 'react-native-gesture-handler' -import {RootSiblingParent} from 'react-native-root-siblings' import { initialWindowMetrics, SafeAreaProvider, @@ -137,64 +136,62 @@ function InnerApp() { - - - - - - - - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index 569c9be79..b7cba6122 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -3,7 +3,6 @@ import '#/view/icons' import './style.css' import React, {useEffect, useState} from 'react' -import {RootSiblingParent} from 'react-native-root-siblings' import {SafeAreaProvider} from 'react-native-safe-area-context' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -111,62 +110,60 @@ function InnerApp() { - - - - - - - - - - - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/yarn.lock b/yarn.lock index a773233e7..dfab6f354 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17071,11 +17071,6 @@ react-native-reanimated@^3.19.1: invariant "^2.2.4" react-native-is-edge-to-edge "1.1.7" -react-native-root-siblings@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-5.0.1.tgz#97e050e5155228f65810fb1c466ff8e769c5272c" - integrity sha512-Ay3k/fBj6ReUkWX5WNS+oEAcgPLEGOK8n7K/L7D85mf3xvd8rm/b4spsv26E4HlFzluVx5HKbxEt9cl0wQ1u3g== - react-native-safe-area-context@~5.6.0: version "5.6.1" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz#cb4d249ef1a6f7e8fd0cfdfa9764838dffda26b6" -- 2.51.2 From d3dbb9468949e5122a18c1e07e28fe76cbeb5d36 Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Mon, 29 Sep 2025 23:12:50 +0300 Subject: [PATCH 6/6] Catch errors on geolocation request, reduce Sentry logs (#9098) --- .../geolocation/useSyncedDeviceGeolocation.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/state/geolocation/useSyncedDeviceGeolocation.ts b/src/state/geolocation/useSyncedDeviceGeolocation.ts index 602f29a30..fea6198d4 100644 --- a/src/state/geolocation/useSyncedDeviceGeolocation.ts +++ b/src/state/geolocation/useSyncedDeviceGeolocation.ts @@ -1,10 +1,45 @@ import {useEffect, useRef} from 'react' import * as Location from 'expo-location' +import {createPermissionHook} from 'expo-modules-core' import {logger} from '#/state/geolocation/logger' import {getDeviceGeolocation} from '#/state/geolocation/util' import {device, useStorage} from '#/storage' +/** + * Location.useForegroundPermissions on web just errors if the navigator.permissions API is not available. + * We need to catch and ignore it, since it's effectively denied. + * @see https://github.com/expo/expo/blob/72f1562ed9cce5ff6dfe04aa415b71632a3d4b87/packages/expo-location/src/Location.ts#L290-L293 + */ +const useForegroundPermissions = createPermissionHook({ + getMethod: () => + Location.getForegroundPermissionsAsync().catch(error => { + logger.debug( + 'useForegroundPermission: error getting location permissions', + {safeMessage: error}, + ) + return { + status: Location.PermissionStatus.DENIED, + granted: false, + canAskAgain: false, + expires: 0, + } + }), + requestMethod: () => + Location.requestForegroundPermissionsAsync().catch(error => { + logger.debug( + 'useForegroundPermission: error requesting location permissions', + {safeMessage: error}, + ) + return { + status: Location.PermissionStatus.DENIED, + granted: false, + canAskAgain: false, + expires: 0, + } + }), +}) + /** * Hook to get and sync the device geolocation from the device GPS and store it * using device storage. If permissions are not granted, it will clear any cached @@ -12,7 +47,7 @@ import {device, useStorage} from '#/storage' */ export function useSyncedDeviceGeolocation() { const synced = useRef(false) - const [status] = Location.useForegroundPermissions() + const [status] = useForegroundPermissions() const [deviceGeolocation, setDeviceGeolocation] = useStorage(device, [ 'deviceGeolocation', ]) -- 2.51.2