Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
bf87d42
Send hybridAppSettings via native method
mateuuszzzzz Jun 30, 2025
e64d00a
Merge branch 'main' into fix-reload-on-new-sign-in-page
mateuuszzzzz Jul 2, 2025
15eed0e
Add log
mateuuszzzzz Jul 2, 2025
296cb7e
Fix typo
mateuuszzzzz Jul 2, 2025
259a514
Apply prettier
mateuuszzzzz Jul 2, 2025
503fc0e
Add missing log in iOS turbo module
mateuuszzzzz Jul 2, 2025
faeece6
Merge branch 'main' into fix-reload-on-new-sign-in-page
mateuuszzzzz Jul 3, 2025
c07874f
Revert "Revert "Bring back new sign-in page""
mateuuszzzzz Jul 3, 2025
cd42183
Add missing import
mateuuszzzzz Jul 3, 2025
b79248d
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 8, 2025
b196416
Merge branch 'implement-hybrid-version-of-get-initial-url' into fix-r…
war-in Jul 8, 2025
6f91ef1
remove newDotSignInState
war-in Jul 9, 2025
f915428
remove unnecessary validate login check
war-in Jul 9, 2025
5342ff5
enable public rooms for anonymus users
war-in Jul 9, 2025
ac045c7
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 10, 2025
bdf8365
Merge branch 'war-in/send-hybridAppSettings-in-native-method' into fi…
war-in Jul 10, 2025
1fe610e
Merge branch 'implement-hybrid-version-of-get-initial-url' into fix-r…
war-in Jul 10, 2025
7dfdfe2
Merge branch 'war-in/create-hybridapp-onyxkey' into fix-reload-on-new…
war-in Jul 10, 2025
a4b8f04
fix closeReactNativeApp imports
war-in Jul 10, 2025
4d0ac15
fix anonymus user sign in
war-in Jul 10, 2025
5fcf9ad
fix lint & prettier
war-in Jul 10, 2025
53b196f
fix typecheck
war-in Jul 10, 2025
5cee6d8
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 11, 2025
3ac8d92
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 14, 2025
0796a99
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 15, 2025
165a319
Merge branch 'implement-hybrid-version-of-get-initial-url' into fix-r…
war-in Jul 15, 2025
ffb0d69
cleanup shouldHideSplash logic
war-in Jul 15, 2025
6d38ec7
fix degraded performance when signing out from OD by bumping react-na…
war-in Jul 15, 2025
16ed9e4
Merge branch 'main' into fix-reload-on-new-sign-in-page
war-in Jul 16, 2025
d461b59
improve signing out from OD
war-in Jul 16, 2025
607190b
explain why we clear OD in SignInPage
war-in Jul 16, 2025
58f88a0
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 18, 2025
cd9ff6c
post-merge fixes
war-in Jul 18, 2025
db74283
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 18, 2025
1b7f447
fix sign-in flow
war-in Jul 18, 2025
a66fc25
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 21, 2025
b848a1c
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 22, 2025
6eb0c44
remove temporary change
war-in Jul 22, 2025
5e21bd5
improve splash screen hiding logic
war-in Jul 22, 2025
857ea6d
revert unnecessary changes
war-in Jul 22, 2025
a819ec6
fix typescript issue
war-in Jul 22, 2025
4fed748
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 23, 2025
7f7064c
set correct google sign-in webClientId
war-in Jul 24, 2025
ec847e0
Merge branch 'main' into fix-reload-on-new-sign-in-page
war-in Jul 24, 2025
cf18531
revert SafeAreaProvider change
war-in Jul 24, 2025
5969e90
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 28, 2025
fc4952f
fix prettier
war-in Jul 28, 2025
6443962
Merge branch 'refs/heads/main' into fix-reload-on-new-sign-in-page
war-in Jul 31, 2025
8641d50
fix broken import in HybridAppHandler.tsx
war-in Jul 31, 2025
18baf7e
fix infinite `Signing out...` iOS dialog
war-in Jul 31, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion __mocks__/@ua/react-native-airship.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ enum EventType {
PushReceived = 'com.airship.push_received',
}

// eslint-disable-next-line no-restricted-syntax
enum PermissionStatus {
Granted = 'granted',
Denied = 'denied',
NotDetermined = 'not_determined',
}

// eslint-disable-next-line @typescript-eslint/no-namespace
namespace iOS {
/**
Expand Down Expand Up @@ -71,4 +78,4 @@ const Airship: Partial<AirshipRoot> = {

export default Airship;

export {EventType, iOS};
export {EventType, iOS, PermissionStatus};
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,22 @@ class ReactNativeHybridApp(reactContext: ReactApplicationContext) :
override fun onURLListenerAdded() {
Log.d(NAME, "`onURLListenerAdded` 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")
}
}
12 changes: 12 additions & 0 deletions modules/hybrid-app/ios/ReactNativeHybridApp.mm
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ - (void)onURLListenerAdded {
NSLog(@"[ReactNativeHybridApp] `onURLListenerAdded` should never be called in standalone `New Expensify` app");
}

- (void)signInToOldDot:(NSString *)autoGeneratedLogin autoGeneratedPassword:(NSString *)autoGeneratedPassword authToken:(NSString *)authToken email:(NSString *)email policyID:(NSString *)policyID {
NSLog(@"[ReactNativeHybridApp] `signInToOldDot` should never be called in standalone `New Expensify` app");
}

- (void)signOutFromOldDot {
NSLog(@"[ReactNativeHybridApp] `signOutFromOldDot` should never be called in standalone `New Expensify` app");
}

- (void)clearOldDotAfterSignOut {
NSLog(@"[ReactNativeHybridApp] `clearOldDotAfterSignOut` should never be called in standalone `New Expensify` app");
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
Expand Down
3 changes: 3 additions & 0 deletions modules/hybrid-app/src/NativeReactNativeHybridApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export interface Spec extends TurboModule {
getHybridAppSettings: () => Promise<string | null>;
getInitialURL(): Promise<string | null>;
onURLListenerAdded: () => void;
signInToOldDot: (autoGeneratedLogin: string, autoGeneratedPassword: string, authToken: string, email: string, policyID: string) => void;
signOutFromOldDot: () => void;
clearOldDotAfterSignOut: () => void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('ReactNativeHybridApp');
9 changes: 9 additions & 0 deletions modules/hybrid-app/src/index.native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ const HybridAppModule: HybridAppModuleType = {
onURLListenerAdded() {
ReactNativeHybridApp.onURLListenerAdded();
},
signInToOldDot({autoGeneratedLogin, autoGeneratedPassword, authToken, email, policyID}) {
ReactNativeHybridApp.signInToOldDot(autoGeneratedLogin, autoGeneratedPassword, authToken, email, policyID);
},
signOutFromOldDot() {
ReactNativeHybridApp.signOutFromOldDot();
},
clearOldDotAfterSignOut() {
ReactNativeHybridApp.clearOldDotAfterSignOut();
},
};

export default HybridAppModule;
12 changes: 12 additions & 0 deletions modules/hybrid-app/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ const HybridAppModule: HybridAppModuleType = {
// eslint-disable-next-line no-console
console.warn('HybridAppModule: `onURLListenerAdded` should never be called on web');
},
signInToOldDot() {
// eslint-disable-next-line no-console
console.warn('HybridAppModule: `signInToOldDot` should never be called on web');
},
signOutFromOldDot() {
// eslint-disable-next-line no-console
console.warn('HybridAppModule: `signOutFromOldDot` should never be called on web');
},
clearOldDotAfterSignOut() {
// eslint-disable-next-line no-console
console.warn('HybridAppModule: `clearOldDotAfterSignOut` should never be called on web');
},
};

export default HybridAppModule;
3 changes: 3 additions & 0 deletions modules/hybrid-app/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ type HybridAppModuleType = {
getHybridAppSettings: () => Promise<string | null>;
getInitialURL(): Promise<string | null>;
onURLListenerAdded: () => void;
signInToOldDot: (args: {autoGeneratedLogin: string; autoGeneratedPassword: string; authToken: string; email: string; policyID: string}) => void;
signOutFromOldDot: () => void;
clearOldDotAfterSignOut: () => void;
};

export default HybridAppModuleType;
91 changes: 51 additions & 40 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import {CurrentReportIDContextProvider} from './hooks/useCurrentReportID';
import useDefaultDragAndDrop from './hooks/useDefaultDragAndDrop';
import HybridAppHandler from './HybridAppHandler';
import OnyxUpdateManager from './libs/actions/OnyxUpdateManager';
import './libs/HybridApp';
import {AttachmentModalContextProvider} from './pages/media/AttachmentModalScreen/AttachmentModalContext';
import './setup/backgroundTask';
import './setup/hybridApp';
Expand Down Expand Up @@ -69,47 +70,57 @@ function App() {
<InitialURLContextProvider>
<HybridAppHandler />
<GestureHandlerRootView style={fill}>
<ComposeProviders
components={[
OnyxListItemProvider,
ThemeProvider,
ThemeStylesProvider,
ThemeIllustrationsProvider,
SafeAreaProvider,
HTMLEngineProvider,
PortalProvider,
SafeArea,
LocaleContextProvider,
PopoverContextProvider,
CurrentReportIDContextProvider,
ScrollOffsetContextProvider,
AttachmentModalContextProvider,
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActionSheetAwareScrollViewProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
KeyboardStateProvider,
SearchRouterContextProvider,
ProductTrainingContextProvider,
InputBlurContextProvider,
FullScreenBlockingViewContextProvider,
FullScreenLoaderContextProvider,
]}
{/* Initialize metrics early to ensure the UI renders even when NewDot is hidden.
This is necessary for iOS HybridApp's SignInPage to appear correctly without the bootsplash.
See: https://github.com/Expensify/App/pull/65178#issuecomment-3139026551
*/}
<SafeAreaProvider
initialMetrics={{
insets: {top: 0, right: 0, bottom: 0, left: 0},
frame: {x: 0, y: 0, width: 0, height: 0},
}}
>
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
<NavigationBar />
</ComposeProviders>
<ComposeProviders
components={[
OnyxListItemProvider,
ThemeProvider,
ThemeStylesProvider,
ThemeIllustrationsProvider,
HTMLEngineProvider,
PortalProvider,
SafeArea,
LocaleContextProvider,
PopoverContextProvider,
CurrentReportIDContextProvider,
ScrollOffsetContextProvider,
AttachmentModalContextProvider,
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActionSheetAwareScrollViewProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
KeyboardStateProvider,
SearchRouterContextProvider,
ProductTrainingContextProvider,
InputBlurContextProvider,
FullScreenBlockingViewContextProvider,
FullScreenLoaderContextProvider,
]}
>
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
<NavigationBar />
</ComposeProviders>
</SafeAreaProvider>
</GestureHandlerRootView>
</InitialURLContextProvider>
</SplashScreenStateContextProvider>
Expand Down
11 changes: 11 additions & 0 deletions src/CONFIG.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,18 @@ export default {
GOOGLE_SIGN_IN: {
// cspell:disable-next-line
WEB_CLIENT_ID: '921154746561-gpsoaqgqfuqrfsjdf8l7vohfkfj7b9up.apps.googleusercontent.com',
// cspell:disable-next-line
IOS_CLIENT_ID: '921154746561-s3uqn2oe4m85tufi6mqflbfbuajrm2i3.apps.googleusercontent.com',
HYBRID_APP: {
// cspell:disable-next-line
IOS_CLIENT_ID: '1008697809946-sh04nqq0hea396s1qdqqbj6ia649odb2.apps.googleusercontent.com',
WEB_CLIENT_ID: {
// cspell:disable-next-line
IOS: '1008697809946-5e095eqem3o6ugtpc2rjf7v880tcp28p.apps.googleusercontent.com',
// cspell:disable-next-line
ANDROID: '240677659774-86pov3adub93cv4b8uj13g7varolmk2l.apps.googleusercontent.com',
},
},
},
GCP_GEOLOCATION_API_KEY: googleGeolocationAPIKey,
FIREBASE_WEB_CONFIG: {
Expand Down
19 changes: 16 additions & 3 deletions src/Expensify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ function Expensify() {
const [currentOnboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED, {canBeMissing: true});
const [currentOnboardingCompanySize] = useOnyx(ONYXKEYS.ONBOARDING_COMPANY_SIZE, {canBeMissing: true});
const [onboardingInitialPath] = useOnyx(ONYXKEYS.ONBOARDING_LAST_VISITED_PATH, {canBeMissing: true});
const [hybridApp] = useOnyx(ONYXKEYS.HYBRID_APP, {canBeMissing: true});

useDebugShortcut();
usePriorityMode();
Expand All @@ -121,10 +122,22 @@ function Expensify() {
const isAuthenticated = useIsAuthenticated();
const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]);

const shouldInit = isNavigationReady && hasAttemptedToOpenPublicRoom && !!preferredLocale;
const isSplashReadyToBeHidden = splashScreenState === CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN;
const isSplashVisible = splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE;
const isHybridAppReady = splashScreenState === CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN && isAuthenticated;
const shouldHideSplash = shouldInit && (CONFIG.IS_HYBRID_APP ? isHybridAppReady : isSplashVisible);

const shouldInit = isNavigationReady && hasAttemptedToOpenPublicRoom && !!preferredLocale;
const shouldHideSplash = (isSplashReadyToBeHidden || isSplashVisible) && shouldInit && !hybridApp?.loggedOutFromOldDot;

// This effect is closing OldDot sign out modal based on splash screen state
useEffect(() => {
if (!isSplashReadyToBeHidden || !shouldInit || !hybridApp?.loggedOutFromOldDot) {
return;
}

setSplashScreenState(CONST.BOOT_SPLASH_STATE.HIDDEN);
HybridAppModule.clearOldDotAfterSignOut();
}, [hybridApp?.loggedOutFromOldDot, isSplashReadyToBeHidden, setSplashScreenState, shouldInit, splashScreenState]);

const initializeClient = () => {
if (!Visibility.isVisible()) {
return;
Expand Down
20 changes: 15 additions & 5 deletions src/HybridAppHandler.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,22 @@ 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 useOnyx from './hooks/useOnyx';
import {parseHybridAppSettings} from './libs/actions/HybridApp';
import {setupNewDotAfterTransitionFromOldDot} from './libs/actions/Session';
import Log from './libs/Log';
import ONYXKEYS from './ONYXKEYS';
import SplashScreenStateContext from './SplashScreenStateContext';
import isLoadingOnyxValue from './types/utils/isLoadingOnyxValue';

function HybridAppHandler() {
const {setSplashScreenState} = useContext(SplashScreenStateContext);
const {splashScreenState, setSplashScreenState} = useContext(SplashScreenStateContext);
const [tryNewDot, tryNewDotMetadata] = useOnyx(ONYXKEYS.NVP_TRY_NEW_DOT, {canBeMissing: true});

const isLoading = isLoadingOnyxValue(tryNewDotMetadata);

useEffect(() => {
if (!CONFIG.IS_HYBRID_APP) {
if (!CONFIG.IS_HYBRID_APP || isLoading) {
return;
}

Expand All @@ -21,11 +28,14 @@ function HybridAppHandler() {
return;
}

signInAfterTransitionFromOldDot(hybridAppSettings).then(() => {
setupNewDotAfterTransitionFromOldDot(parseHybridAppSettings(hybridAppSettings), tryNewDot).then(() => {
if (splashScreenState !== CONST.BOOT_SPLASH_STATE.VISIBLE) {
return;
}
setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
});
});
}, [setSplashScreenState]);
}, [isLoading, setSplashScreenState, splashScreenState, tryNewDot]);

return null;
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/ScreenWrapper/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import NarrowPaneContext from '@libs/Navigation/AppNavigator/Navigators/NarrowPa
import Navigation from '@libs/Navigation/Navigation';
import type {PlatformStackNavigationProp} from '@libs/Navigation/PlatformStackNavigation/types';
import type {ReportsSplitNavigatorParamList, RootNavigatorParamList} from '@libs/Navigation/types';
import closeReactNativeApp from '@userActions/HybridApp';
import {closeReactNativeApp} from '@userActions/HybridApp';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand Down
20 changes: 16 additions & 4 deletions src/components/SignInButtons/GoogleSignIn/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import {GoogleSignin, statusCodes} from '@react-native-google-signin/google-signin';
import React from 'react';
import IconButton from '@components/SignInButtons/IconButton';
import getPlatform from '@libs/getPlatform';
import Log from '@libs/Log';
import * as Session from '@userActions/Session';
import {beginGoogleSignIn} from '@userActions/Session';
import CONFIG from '@src/CONFIG';
import CONST from '@src/CONST';
import type {GoogleSignInProps} from '.';
import type GoogleError from './types';

/**
* Helper function returning webClientId based on a platform used
*/
function getWebClientId() {
if (!CONFIG.IS_HYBRID_APP) {
return CONFIG.GOOGLE_SIGN_IN.WEB_CLIENT_ID;
}

return getPlatform() === CONST.PLATFORM.ANDROID ? CONFIG.GOOGLE_SIGN_IN.HYBRID_APP.WEB_CLIENT_ID.ANDROID : CONFIG.GOOGLE_SIGN_IN.HYBRID_APP.WEB_CLIENT_ID.IOS;
}

/**
* Google Sign In method for iOS and android that returns identityToken.
*/
function googleSignInRequest() {
GoogleSignin.configure({
webClientId: CONFIG.GOOGLE_SIGN_IN.WEB_CLIENT_ID,
iosClientId: CONFIG.GOOGLE_SIGN_IN.IOS_CLIENT_ID,
webClientId: getWebClientId(),
iosClientId: CONFIG.IS_HYBRID_APP ? CONFIG.GOOGLE_SIGN_IN.HYBRID_APP.IOS_CLIENT_ID : CONFIG.GOOGLE_SIGN_IN.IOS_CLIENT_ID,
offlineAccess: false,
});

Expand All @@ -25,7 +37,7 @@ function googleSignInRequest() {

GoogleSignin.signIn()
.then((response) => response.idToken)
.then((token) => Session.beginGoogleSignIn(token))
.then((token) => beginGoogleSignIn(token))
.catch((error: GoogleError | undefined) => {
// Handle unexpected error shape
if (error?.code === undefined) {
Expand Down
Loading
Loading