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 (
         <StrictModeWrapper>
             <SplashScreenStateContextProvider>
-                <InitialURLContextProvider
-                    url={url}
-                    hybridAppSettings={hybridAppSettings}
-                    timestamp={timestamp}
-                >
+                <InitialURLContextProvider url={url}>
                     <GestureHandlerRootView style={fill}>
                         <ComposeProviders
                             components={[
@@ -119,6 +114,12 @@ function App({url, hybridAppSettings, timestamp}: AppProps) {
                         >
                             <CustomStatusBarAndBackground />
                             <ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
+                                {CONFIG.IS_HYBRID_APP && (
+                                    <HybridAppHandler
+                                        url={url}
+                                        hybridAppSettings={hybridAppSettings}
+                                    />
+                                )}
                                 <ColorSchemeWrapper>
                                     <Expensify />
                                 </ColorSchemeWrapper>
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<Route | undefined>();
-    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 <war-in@users.noreply.github.com>
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<Spec>('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 <war-in@users.noreply.github.com>
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<Spec>('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();
