From 140136411811cfa29af11f32615eb284bda935c4 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Mon, 30 Jun 2025 13:06:55 +0200 Subject: [PATCH 1/7] Send hybridAppSettings via native method --- .../ReactNativeHybridApp.kt | 5 +++++ .../hybrid-app/ios/ReactNativeHybridApp.mm | 5 +++++ .../src/NativeReactNativeHybridApp.ts | 1 + modules/hybrid-app/src/index.native.ts | 3 +++ modules/hybrid-app/src/index.ts | 5 +++++ modules/hybrid-app/src/types.ts | 1 + src/App.tsx | 6 ++---- src/HybridAppHandler.tsx | 21 +++++++++++++------ 8 files changed, 37 insertions(+), 10 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 9a743490ffa3..f8301df4b58b 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 @@ -3,6 +3,7 @@ package com.expensify.reactnativehybridapp import com.facebook.react.bridge.ReactApplicationContext import com.facebook.react.module.annotations.ReactModule import android.util.Log +import com.facebook.react.bridge.Promise @ReactModule(name = NativeReactNativeHybridAppSpec.NAME) class ReactNativeHybridApp(reactContext: ReactApplicationContext) : @@ -36,4 +37,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 getHybridAppSettings(promise: Promise) { + promise.reject("NOT_IMPLEMENTED", "getHybridAppSettings is not implemented in standalone New Expensify app") + } } diff --git a/modules/hybrid-app/ios/ReactNativeHybridApp.mm b/modules/hybrid-app/ios/ReactNativeHybridApp.mm index 6b093b3f26b5..258dd2d2bcd5 100644 --- a/modules/hybrid-app/ios/ReactNativeHybridApp.mm +++ b/modules/hybrid-app/ios/ReactNativeHybridApp.mm @@ -27,6 +27,11 @@ - (void)sendAuthToken:(NSString *)authToken { NSLog(@"[ReactNativeHybridApp] `sendAuthToken` should never be called in standalone `New Expensify` app"); } +- (void)getHybridAppSettings:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject { + reject(@"NOT_IMPLEMENTED", @"This method is not available in standalone New Expensify app", nil); +} + - (std::shared_ptr)getTurboModule: (const facebook::react::ObjCTurboModule::InitParams &)params { diff --git a/modules/hybrid-app/src/NativeReactNativeHybridApp.ts b/modules/hybrid-app/src/NativeReactNativeHybridApp.ts index a18f44f87d50..a0bf73e1f942 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; + getHybridAppSettings: () => Promise; } 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..229589cb255d 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); }, + getHybridAppSettings() { + return ReactNativeHybridApp.getHybridAppSettings(); + }, }; export default HybridAppModule; diff --git a/modules/hybrid-app/src/index.ts b/modules/hybrid-app/src/index.ts index ca45aeeb2075..18c248ec7639 100644 --- a/modules/hybrid-app/src/index.ts +++ b/modules/hybrid-app/src/index.ts @@ -24,6 +24,11 @@ const HybridAppModule: HybridAppModuleType = { // eslint-disable-next-line no-console console.warn('HybridAppModule: `sendAuthToken` should never be called on web'); }, + getHybridAppSettings() { + // eslint-disable-next-line no-console + console.warn('HybridAppModule: `getHybridAppSettings` should never be called on web'); + return Promise.resolve(null); + }, }; export default HybridAppModule; diff --git a/modules/hybrid-app/src/types.ts b/modules/hybrid-app/src/types.ts index 8a40a1fc5767..90a44ab380de 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; + getHybridAppSettings: () => Promise; }; export default HybridAppModuleType; diff --git a/src/App.tsx b/src/App.tsx index dc17fe242a55..2cf399fb5edf 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -54,8 +54,6 @@ import {SplashScreenStateContextProvider} from './SplashScreenStateContext'; type AppProps = { /** The URL specifying the initial navigation destination when the app opens */ url?: Route; - /** Serialized configuration data required to initialize the React Native app (e.g. authentication details) */ - hybridAppSettings?: string; }; LogBox.ignoreLogs([ @@ -71,7 +69,7 @@ const fill = {flex: 1}; const StrictModeWrapper = CONFIG.USE_REACT_STRICT_MODE_IN_DEV ? React.StrictMode : ({children}: {children: React.ReactElement}) => children; -function App({url, hybridAppSettings}: AppProps) { +function App({url}: AppProps) { useDefaultDragAndDrop(); OnyxUpdateManager(); @@ -79,7 +77,7 @@ function App({url, hybridAppSettings}: AppProps) { - + { - setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); - setSignInHandled(true); + HybridAppModule.getHybridAppSettings().then((hybridAppSettings: string | null) => { + if(!hybridAppSettings) { + // Native method can send non-null value only once per NewDot lifecycle. It prevents issues with multiple initializations during reloads on debug builds. + Log.info('[HybridApp] `getHybridAppSettings` called more than once during single NewDot lifecycle. Skipping initialization.'); + return; + } + + signInAfterTransitionFromOldDot(hybridAppSettings).then(() => { + setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); + setSignInHandled(true); + }); }); return null; From 7fb3e71e43eefde7a1ebd0a32f161ec3cefac370 Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 2 Jul 2025 09:00:26 +0200 Subject: [PATCH 2/7] Add log --- .../ReactNativeHybridApp.kt | 19 +++++++++++++++++++ 1 file changed, 19 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 f8301df4b58b..ddd8966807ce 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 @@ -38,7 +38,26 @@ class ReactNativeHybridApp(reactContext: ReactApplicationContext) : Log.d(NAME, "`sendAuthToken` should never be called in standalone `New Expensify` app") } + override fun signInToOldDot( + autoGeneratedLogin: String, + autoGeneratedPassword: String, + authToken: String, + email: String, + policyID: String + ) { + Log.d(NAME, "`signInToOldDot` should never be called in standalone `New Expensify` app") + } + + override fun signOutFromOldDot() { + Log.d(NAME, "`signOutFromOldDot` should never be called in standalone `New Expensify` app") + } + + override fun clearOldDotAfterSignOut() { + Log.d(NAME, "`clearOldDotAfterSignOut` should never be called in standalone `New Expensify` app") + } + override fun getHybridAppSettings(promise: Promise) { + Log.d(NAME, "`getHybridAppSettings` should never be called in standalone `New Expensify` app") promise.reject("NOT_IMPLEMENTED", "getHybridAppSettings is not implemented in standalone New Expensify app") } } From 7c818c4f79bd21ec08bbfcfce47fd4d507e79a2a Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 2 Jul 2025 09:05:34 +0200 Subject: [PATCH 3/7] Fix typo --- src/HybridAppHandler.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/HybridAppHandler.tsx b/src/HybridAppHandler.tsx index b951b9a7a31d..0c3df401d666 100644 --- a/src/HybridAppHandler.tsx +++ b/src/HybridAppHandler.tsx @@ -1,6 +1,6 @@ import {useContext, useState} from 'react'; import HybridAppModule from "@expensify/react-native-hybrid-app/src/index.native"; -import Log from './libs/__mocks__/Log'; +import Log from './libs/Log'; import CONFIG from './CONFIG'; import CONST from './CONST'; import {signInAfterTransitionFromOldDot} from './libs/actions/Session'; From 1f18a18a0f3b5ddabb2ac14fac3feca858e1695f Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 2 Jul 2025 09:49:06 +0200 Subject: [PATCH 4/7] Apply prettier & move HybridAppHandler logic to useEffect --- src/App.tsx | 2 +- src/HybridAppHandler.tsx | 33 +++++++++++++++++---------------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 2cf399fb5edf..6eac37c6f2db 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -77,7 +77,7 @@ function App({url}: AppProps) { - + { - if(!hybridAppSettings) { - // Native method can send non-null value only once per NewDot lifecycle. It prevents issues with multiple initializations during reloads on debug builds. - Log.info('[HybridApp] `getHybridAppSettings` called more than once during single NewDot lifecycle. Skipping initialization.'); + useEffect(() => { + if (!CONFIG.IS_HYBRID_APP) { return; } - signInAfterTransitionFromOldDot(hybridAppSettings).then(() => { - setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); - setSignInHandled(true); + HybridAppModule.getHybridAppSettings().then((hybridAppSettings: string | null) => { + if(!hybridAppSettings) { + // Native method can send non-null value only once per NewDot lifecycle. It prevents issues with multiple initializations during reloads on debug builds. + Log.info('[HybridApp] `getHybridAppSettings` called more than once during single NewDot lifecycle. Skipping initialization.'); + return; + } + + signInAfterTransitionFromOldDot(hybridAppSettings).then(() => { + setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN); + }); }); - }); + }, [setSplashScreenState]); return null; } From 64bcc695b904af5747094054a9dc517f9f21ef7d Mon Sep 17 00:00:00 2001 From: Mateusz Rajski Date: Wed, 2 Jul 2025 10:00:49 +0200 Subject: [PATCH 5/7] Add missing log in iOS turbo module --- modules/hybrid-app/ios/ReactNativeHybridApp.mm | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/hybrid-app/ios/ReactNativeHybridApp.mm b/modules/hybrid-app/ios/ReactNativeHybridApp.mm index 258dd2d2bcd5..287d8be1164a 100644 --- a/modules/hybrid-app/ios/ReactNativeHybridApp.mm +++ b/modules/hybrid-app/ios/ReactNativeHybridApp.mm @@ -29,6 +29,7 @@ - (void)sendAuthToken:(NSString *)authToken { - (void)getHybridAppSettings:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject { + NSLog(@"[ReactNativeHybridApp] `getHybridAppSettings` should never be called in standalone `New Expensify` app"); reject(@"NOT_IMPLEMENTED", @"This method is not available in standalone New Expensify app", nil); } From 84247d972dd52bd0a8ff83df024e0d651ed5c3e1 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 8 Jul 2025 12:48:22 +0200 Subject: [PATCH 6/7] fix prettier --- src/HybridAppHandler.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/HybridAppHandler.tsx b/src/HybridAppHandler.tsx index b0018c755511..2b425b34072d 100644 --- a/src/HybridAppHandler.tsx +++ b/src/HybridAppHandler.tsx @@ -1,12 +1,11 @@ -import HybridAppModule from "@expensify/react-native-hybrid-app/src/index.native"; +import HybridAppModule from '@expensify/react-native-hybrid-app'; import {useContext, useEffect} from 'react'; import CONFIG from './CONFIG'; import CONST from './CONST'; -import { signInAfterTransitionFromOldDot } from './libs/actions/Session'; +import {signInAfterTransitionFromOldDot} from './libs/actions/Session'; import Log from './libs/Log'; import SplashScreenStateContext from './SplashScreenStateContext'; - function HybridAppHandler() { const {setSplashScreenState} = useContext(SplashScreenStateContext); @@ -16,7 +15,7 @@ function HybridAppHandler() { } HybridAppModule.getHybridAppSettings().then((hybridAppSettings: string | null) => { - if(!hybridAppSettings) { + if (!hybridAppSettings) { // Native method can send non-null value only once per NewDot lifecycle. It prevents issues with multiple initializations during reloads on debug builds. Log.info('[HybridApp] `getHybridAppSettings` called more than once during single NewDot lifecycle. Skipping initialization.'); return; From 46b089d4fef44949a8926c80d720ffabe3efa7e7 Mon Sep 17 00:00:00 2001 From: war-in Date: Tue, 8 Jul 2025 12:54:40 +0200 Subject: [PATCH 7/7] remove unrelated code --- .../ReactNativeHybridApp.kt | 18 ------------------ 1 file changed, 18 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 ddd8966807ce..02216f889603 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 @@ -38,24 +38,6 @@ class ReactNativeHybridApp(reactContext: ReactApplicationContext) : Log.d(NAME, "`sendAuthToken` should never be called in standalone `New Expensify` app") } - override fun signInToOldDot( - autoGeneratedLogin: String, - autoGeneratedPassword: String, - authToken: String, - email: String, - policyID: String - ) { - Log.d(NAME, "`signInToOldDot` should never be called in standalone `New Expensify` app") - } - - override fun signOutFromOldDot() { - Log.d(NAME, "`signOutFromOldDot` should never be called in standalone `New Expensify` app") - } - - override fun clearOldDotAfterSignOut() { - Log.d(NAME, "`clearOldDotAfterSignOut` should never be called in standalone `New Expensify` app") - } - override fun getHybridAppSettings(promise: Promise) { Log.d(NAME, "`getHybridAppSettings` should never be called in standalone `New Expensify` app") promise.reject("NOT_IMPLEMENTED", "getHybridAppSettings is not implemented in standalone New Expensify app")