From e51f63c53c8aefb1e8aec4cb44dc85cff4cf5490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Warcho=C5=82?= <61014013+war-in@users.noreply.github.com> Date: Fri, 30 May 2025 17:36:14 +0200 Subject: [PATCH 1/3] Revert "[CP staging] Revert "Improve HybridApp initialProps"" --- src/App.tsx | 17 +++--- src/Expensify.tsx | 4 ++ src/HybridAppHandler.tsx | 57 ++++++++++++++++++++ src/components/InitialURLContextProvider.tsx | 37 ++----------- 4 files changed, 74 insertions(+), 41 deletions(-) create mode 100644 src/HybridAppHandler.tsx diff --git a/src/App.tsx b/src/App.tsx index e7dd3684ef39..5d2450347b10 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -39,6 +39,7 @@ import CONFIG from './CONFIG'; import Expensify from './Expensify'; import {CurrentReportIDContextProvider} from './hooks/useCurrentReportID'; import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop'; +import HybridAppHandler from './HybridAppHandler'; import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; import type {Route} from './ROUTES'; @@ -54,8 +55,6 @@ type AppProps = { url?: Route; /** Serialized configuration data required to initialize the React Native app (e.g. authentication details) */ hybridAppSettings?: string; - /** A timestamp indicating when the initial properties were last updated, used to detect changes */ - timestamp?: string; }; LogBox.ignoreLogs([ @@ -71,18 +70,14 @@ const fill = {flex: 1}; const StrictModeWrapper = CONFIG.USE_REACT_STRICT_MODE_IN_DEV ? React.StrictMode : ({children}: {children: React.ReactElement}) => children; -function App({url, hybridAppSettings, timestamp}: AppProps) { +function App({url, hybridAppSettings}: AppProps) { useDefaultDragAndDrop(); OnyxUpdateManager(); return ( - + + {CONFIG.IS_HYBRID_APP && ( + + )} diff --git a/src/Expensify.tsx b/src/Expensify.tsx index 5c60815cbedb..ad33fca33fbe 100644 --- a/src/Expensify.tsx +++ b/src/Expensify.tsx @@ -215,6 +215,10 @@ function Expensify() { // Open chat report from a deep link (only mobile native) Linking.addEventListener('url', (state) => { + // We use custom deeplink handler in HybridAppHandler + if (CONFIG.IS_HYBRID_APP) { + return; + } Report.openReportFromDeepLink(state.url); }); diff --git a/src/HybridAppHandler.tsx b/src/HybridAppHandler.tsx new file mode 100644 index 000000000000..61401c63e5a2 --- /dev/null +++ b/src/HybridAppHandler.tsx @@ -0,0 +1,57 @@ +import {findFocusedRoute} from '@react-navigation/native'; +import {useContext, useEffect, useState} from 'react'; +import {Linking} from 'react-native'; +import type {AppProps} from './App'; +import CONST from './CONST'; +import {signInAfterTransitionFromOldDot} from './libs/actions/Session'; +import Navigation, {navigationRef} from './libs/Navigation/Navigation'; +import type {Route} from './ROUTES'; +import ROUTES from './ROUTES'; +import SCREENS from './SCREENS'; +import SplashScreenStateContext from './SplashScreenStateContext'; + +function handleHybridUrlNavigation(url: Route) { + const parsedUrl = Navigation.parseHybridAppUrl(url); + + Navigation.isNavigationReady().then(() => { + if (parsedUrl.startsWith(`/${ROUTES.SHARE_ROOT}`)) { + const focusRoute = findFocusedRoute(navigationRef.getRootState()); + if (focusRoute?.name === SCREENS.SHARE.SHARE_DETAILS || focusRoute?.name === SCREENS.SHARE.SUBMIT_DETAILS) { + Navigation.goBack(ROUTES.SHARE_ROOT); + return; + } + } + Navigation.navigate(parsedUrl); + }); +} + +function HybridAppHandler({url, hybridAppSettings}: AppProps) { + const [signInHandled, setSignInHandled] = useState(false); + const {setSplashScreenState} = useContext(SplashScreenStateContext); + + useEffect(() => { + const listener = Linking.addEventListener('url', (state) => { + handleHybridUrlNavigation(state.url as Route); + }); + + return () => { + listener.remove(); + }; + }, []); + + if (!url || !hybridAppSettings || signInHandled) { + return null; + } + + signInAfterTransitionFromOldDot(hybridAppSettings).then(() => { + handleHybridUrlNavigation(url); + setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); + setSignInHandled(true); + }); + + return null; +} + +HybridAppHandler.displayName = 'HybridAppHandler'; + +export default HybridAppHandler; diff --git a/src/components/InitialURLContextProvider.tsx b/src/components/InitialURLContextProvider.tsx index c1a0ce3cbd89..1aa40d6c1d7a 100644 --- a/src/components/InitialURLContextProvider.tsx +++ b/src/components/InitialURLContextProvider.tsx @@ -1,15 +1,8 @@ -import {findFocusedRoute} from '@react-navigation/native'; import React, {createContext, useEffect, useMemo, useState} from 'react'; import type {ReactNode} from 'react'; import {Linking} from 'react-native'; -import {signInAfterTransitionFromOldDot} from '@libs/actions/Session'; -import Navigation, {navigationRef} from '@navigation/Navigation'; import type {AppProps} from '@src/App'; -import CONST from '@src/CONST'; import type {Route} from '@src/ROUTES'; -import ROUTES from '@src/ROUTES'; -import SCREENS from '@src/SCREENS'; -import {useSplashScreenStateContext} from '@src/SplashScreenStateContext'; type InitialUrlContextType = { initialURL: Route | undefined; @@ -27,40 +20,18 @@ type InitialURLContextProviderProps = AppProps & { children: ReactNode; }; -function InitialURLContextProvider({children, url, hybridAppSettings, timestamp}: InitialURLContextProviderProps) { +function InitialURLContextProvider({children, url}: InitialURLContextProviderProps) { const [initialURL, setInitialURL] = useState(); - const {splashScreenState, setSplashScreenState} = useSplashScreenStateContext(); useEffect(() => { - if (url && hybridAppSettings) { - signInAfterTransitionFromOldDot(hybridAppSettings).then(() => { - setInitialURL(url); - - const parsedUrl = Navigation.parseHybridAppUrl(url); - - Navigation.isNavigationReady().then(() => { - if (parsedUrl.startsWith(`/${ROUTES.SHARE_ROOT}`)) { - const focusRoute = findFocusedRoute(navigationRef.getRootState()); - if (focusRoute?.name === SCREENS.SHARE.SHARE_DETAILS || focusRoute?.name === SCREENS.SHARE.SUBMIT_DETAILS) { - Navigation.goBack(ROUTES.SHARE_ROOT); - return; - } - } - Navigation.navigate(parsedUrl); - }); - - if (splashScreenState === CONST.BOOT_SPLASH_STATE.HIDDEN) { - return; - } - setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); - }); + if (url) { + setInitialURL(url); return; } Linking.getInitialURL().then((initURL) => { setInitialURL(initURL as Route); }); - // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps - }, [url, hybridAppSettings, timestamp]); + }, [url]); const initialUrlContext = useMemo( () => ({ From f7a01f31edfc74752e1eda3425abd5227e006bdf Mon Sep 17 00:00:00 2001 From: war-in Date: Mon, 2 Jun 2025 11:51:53 +0200 Subject: [PATCH 2/3] fix deploy blocker --- .../expensify/reactnativehybridapp/ReactNativeHybridApp.kt | 4 ++++ modules/hybrid-app/src/NativeReactNativeHybridApp.ts | 1 + modules/hybrid-app/src/index.native.ts | 3 +++ modules/hybrid-app/src/index.ts | 4 ++++ modules/hybrid-app/src/types.ts | 1 + src/HybridAppHandler.tsx | 2 ++ 6 files changed, 15 insertions(+) diff --git a/modules/hybrid-app/android/src/main/java/com/expensify/reactnativehybridapp/ReactNativeHybridApp.kt b/modules/hybrid-app/android/src/main/java/com/expensify/reactnativehybridapp/ReactNativeHybridApp.kt index 9a743490ffa3..21fdf20b5907 100644 --- a/modules/hybrid-app/android/src/main/java/com/expensify/reactnativehybridapp/ReactNativeHybridApp.kt +++ b/modules/hybrid-app/android/src/main/java/com/expensify/reactnativehybridapp/ReactNativeHybridApp.kt @@ -36,4 +36,8 @@ class ReactNativeHybridApp(reactContext: ReactApplicationContext) : override fun sendAuthToken(authToken: String?) { Log.d(NAME, "`sendAuthToken` should never be called in standalone `New Expensify` app") } + + override fun allowSendingThroughLinking() { + Log.d(NAME, "`allowSendingThroughLinking` should never be called in standalone `New Expensify` app") + } } diff --git a/modules/hybrid-app/src/NativeReactNativeHybridApp.ts b/modules/hybrid-app/src/NativeReactNativeHybridApp.ts index a18f44f87d50..c3a0abc84122 100644 --- a/modules/hybrid-app/src/NativeReactNativeHybridApp.ts +++ b/modules/hybrid-app/src/NativeReactNativeHybridApp.ts @@ -9,6 +9,7 @@ export interface Spec extends TurboModule { completeOnboarding: (status: boolean) => void; switchAccount: (newDotCurrentAccountEmail: string, authToken: string, policyID: string, accountID: string) => void; sendAuthToken: (authToken: string) => void; + allowSendingThroughLinking: () => void; } export default TurboModuleRegistry.getEnforcing('ReactNativeHybridApp'); diff --git a/modules/hybrid-app/src/index.native.ts b/modules/hybrid-app/src/index.native.ts index 50dfd719449a..ec37d6ac7b29 100644 --- a/modules/hybrid-app/src/index.native.ts +++ b/modules/hybrid-app/src/index.native.ts @@ -20,6 +20,9 @@ const HybridAppModule: HybridAppModuleType = { sendAuthToken({authToken}) { ReactNativeHybridApp.sendAuthToken(authToken); }, + allowSendingThroughLinking() { + ReactNativeHybridApp.allowSendingThroughLinking(); + } }; export default HybridAppModule; diff --git a/modules/hybrid-app/src/index.ts b/modules/hybrid-app/src/index.ts index ca45aeeb2075..f41ad9049a92 100644 --- a/modules/hybrid-app/src/index.ts +++ b/modules/hybrid-app/src/index.ts @@ -24,6 +24,10 @@ const HybridAppModule: HybridAppModuleType = { // eslint-disable-next-line no-console console.warn('HybridAppModule: `sendAuthToken` should never be called on web'); }, + allowSendingThroughLinking() { + // eslint-disable-next-line no-console + console.warn('HybridAppModule: `allowSendingThroughLinking` should never be called on web'); + } }; export default HybridAppModule; diff --git a/modules/hybrid-app/src/types.ts b/modules/hybrid-app/src/types.ts index 8a40a1fc5767..397d76a63a83 100644 --- a/modules/hybrid-app/src/types.ts +++ b/modules/hybrid-app/src/types.ts @@ -5,6 +5,7 @@ type HybridAppModuleType = { completeOnboarding: (args: {status: boolean}) => void; switchAccount: (args: {newDotCurrentAccountEmail: string; authToken: string; policyID: string; accountID: string}) => void; sendAuthToken: (args: {authToken: string}) => void; + allowSendingThroughLinking: () => void; }; export default HybridAppModuleType; diff --git a/src/HybridAppHandler.tsx b/src/HybridAppHandler.tsx index 61401c63e5a2..e689c62a1acc 100644 --- a/src/HybridAppHandler.tsx +++ b/src/HybridAppHandler.tsx @@ -1,3 +1,4 @@ +import HybridAppModule from '@expensify/react-native-hybrid-app'; import {findFocusedRoute} from '@react-navigation/native'; import {useContext, useEffect, useState} from 'react'; import {Linking} from 'react-native'; @@ -33,6 +34,7 @@ function HybridAppHandler({url, hybridAppSettings}: AppProps) { const listener = Linking.addEventListener('url', (state) => { handleHybridUrlNavigation(state.url as Route); }); + HybridAppModule.allowSendingThroughLinking(); return () => { listener.remove(); From 4cfce346575034d65d117f034fc379c3cdff5c01 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 3 Jun 2025 11:05:22 +0200 Subject: [PATCH 3/3] Revert "fix deploy blocker" This reverts commit f7a01f31edfc74752e1eda3425abd5227e006bdf. --- .../expensify/reactnativehybridapp/ReactNativeHybridApp.kt | 4 ---- modules/hybrid-app/src/NativeReactNativeHybridApp.ts | 1 - modules/hybrid-app/src/index.native.ts | 3 --- modules/hybrid-app/src/index.ts | 4 ---- modules/hybrid-app/src/types.ts | 1 - src/HybridAppHandler.tsx | 2 -- 6 files changed, 15 deletions(-) diff --git a/modules/hybrid-app/android/src/main/java/com/expensify/reactnativehybridapp/ReactNativeHybridApp.kt b/modules/hybrid-app/android/src/main/java/com/expensify/reactnativehybridapp/ReactNativeHybridApp.kt index 21fdf20b5907..9a743490ffa3 100644 --- a/modules/hybrid-app/android/src/main/java/com/expensify/reactnativehybridapp/ReactNativeHybridApp.kt +++ b/modules/hybrid-app/android/src/main/java/com/expensify/reactnativehybridapp/ReactNativeHybridApp.kt @@ -36,8 +36,4 @@ class ReactNativeHybridApp(reactContext: ReactApplicationContext) : override fun sendAuthToken(authToken: String?) { Log.d(NAME, "`sendAuthToken` should never be called in standalone `New Expensify` app") } - - override fun allowSendingThroughLinking() { - Log.d(NAME, "`allowSendingThroughLinking` should never be called in standalone `New Expensify` app") - } } diff --git a/modules/hybrid-app/src/NativeReactNativeHybridApp.ts b/modules/hybrid-app/src/NativeReactNativeHybridApp.ts index c3a0abc84122..a18f44f87d50 100644 --- a/modules/hybrid-app/src/NativeReactNativeHybridApp.ts +++ b/modules/hybrid-app/src/NativeReactNativeHybridApp.ts @@ -9,7 +9,6 @@ export interface Spec extends TurboModule { completeOnboarding: (status: boolean) => void; switchAccount: (newDotCurrentAccountEmail: string, authToken: string, policyID: string, accountID: string) => void; sendAuthToken: (authToken: string) => void; - allowSendingThroughLinking: () => void; } export default TurboModuleRegistry.getEnforcing('ReactNativeHybridApp'); diff --git a/modules/hybrid-app/src/index.native.ts b/modules/hybrid-app/src/index.native.ts index ec37d6ac7b29..50dfd719449a 100644 --- a/modules/hybrid-app/src/index.native.ts +++ b/modules/hybrid-app/src/index.native.ts @@ -20,9 +20,6 @@ const HybridAppModule: HybridAppModuleType = { sendAuthToken({authToken}) { ReactNativeHybridApp.sendAuthToken(authToken); }, - allowSendingThroughLinking() { - ReactNativeHybridApp.allowSendingThroughLinking(); - } }; export default HybridAppModule; diff --git a/modules/hybrid-app/src/index.ts b/modules/hybrid-app/src/index.ts index f41ad9049a92..ca45aeeb2075 100644 --- a/modules/hybrid-app/src/index.ts +++ b/modules/hybrid-app/src/index.ts @@ -24,10 +24,6 @@ const HybridAppModule: HybridAppModuleType = { // eslint-disable-next-line no-console console.warn('HybridAppModule: `sendAuthToken` should never be called on web'); }, - allowSendingThroughLinking() { - // eslint-disable-next-line no-console - console.warn('HybridAppModule: `allowSendingThroughLinking` should never be called on web'); - } }; export default HybridAppModule; diff --git a/modules/hybrid-app/src/types.ts b/modules/hybrid-app/src/types.ts index 397d76a63a83..8a40a1fc5767 100644 --- a/modules/hybrid-app/src/types.ts +++ b/modules/hybrid-app/src/types.ts @@ -5,7 +5,6 @@ type HybridAppModuleType = { completeOnboarding: (args: {status: boolean}) => void; switchAccount: (args: {newDotCurrentAccountEmail: string; authToken: string; policyID: string; accountID: string}) => void; sendAuthToken: (args: {authToken: string}) => void; - allowSendingThroughLinking: () => void; }; export default HybridAppModuleType; diff --git a/src/HybridAppHandler.tsx b/src/HybridAppHandler.tsx index e689c62a1acc..61401c63e5a2 100644 --- a/src/HybridAppHandler.tsx +++ b/src/HybridAppHandler.tsx @@ -1,4 +1,3 @@ -import HybridAppModule from '@expensify/react-native-hybrid-app'; import {findFocusedRoute} from '@react-navigation/native'; import {useContext, useEffect, useState} from 'react'; import {Linking} from 'react-native'; @@ -34,7 +33,6 @@ function HybridAppHandler({url, hybridAppSettings}: AppProps) { const listener = Linking.addEventListener('url', (state) => { handleHybridUrlNavigation(state.url as Route); }); - HybridAppModule.allowSendingThroughLinking(); return () => { listener.remove();