Skip to content

Commit

Permalink
Merge branch 'make-new-dot-signin-default-for-hybridapp' into war-in/…
Browse files Browse the repository at this point in the history
…add-named-params-to-HybridAppModule-methods

# Conflicts:
#	src/libs/actions/Session/index.ts
#	src/types/modules/react-native.d.ts
  • Loading branch information
war-in committed Dec 13, 2024
2 parents 7275e6a + 837cbfe commit 74d0c59
Show file tree
Hide file tree
Showing 24 changed files with 364 additions and 118 deletions.
12 changes: 9 additions & 3 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,12 @@ import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsCo
import type {Route} from './ROUTES';
import {SplashScreenStateContextProvider} from './SplashScreenStateContext';

/**
* URL and settings passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds.
*/
type AppProps = {
/** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */
url?: Route;
hybridAppSettings?: string;
};

LogBox.ignoreLogs([
Expand All @@ -59,14 +62,17 @@ const fill = {flex: 1};

const StrictModeWrapper = CONFIG.USE_REACT_STRICT_MODE_IN_DEV ? React.StrictMode : ({children}: {children: React.ReactElement}) => children;

function App({url}: AppProps) {
function App({url, hybridAppSettings}: AppProps) {
useDefaultDragAndDrop();
OnyxUpdateManager();

return (
<StrictModeWrapper>
<SplashScreenStateContextProvider>
<InitialURLContextProvider url={url}>
<InitialURLContextProvider
url={url}
hybridAppSettings={hybridAppSettings}
>
<GestureHandlerRootView style={fill}>
<ComposeProviders
components={[
Expand Down
9 changes: 9 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4648,6 +4648,9 @@ const CONST = {
},
EVENTS: {
SCROLLING: 'scrolling',
HYBRID_APP: {
ON_SIGN_IN_FINISHED: 'onSignInFinished',
},
},

CHAT_HEADER_LOADER_HEIGHT: 36,
Expand Down Expand Up @@ -6305,6 +6308,12 @@ const CONST = {
HIDDEN: `hidden`,
},

HYBRID_APP_SIGN_IN_STATE: {
NOT_STARTED: 'notStarted',
STARTED: 'started',
FINISHED: 'finished',
},

CSV_IMPORT_COLUMNS: {
EMAIL: 'email',
NAME: 'name',
Expand Down
9 changes: 7 additions & 2 deletions src/Expensify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ function Expensify() {
const [screenShareRequest] = useOnyx(ONYXKEYS.SCREEN_SHARE_REQUEST);
const [focusModeNotification] = useOnyx(ONYXKEYS.FOCUS_MODE_NOTIFICATION, {initWithStoredValues: false});
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH);
const [hybridApp] = useOnyx(ONYXKEYS.HYBRID_APP);

useEffect(() => {
if (!account?.needsTwoFactorAuthSetup || account.requiresTwoFactorAuth) {
Expand All @@ -115,10 +116,14 @@ function Expensify() {
const isAuthenticated = useMemo(() => !!(session?.authToken ?? null), [session]);
const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]);

const shouldInit = isNavigationReady && hasAttemptedToOpenPublicRoom;
const shouldInit = NativeModules.HybridAppModule
? !hybridApp?.loggedOutFromOldDot && isNavigationReady && hasAttemptedToOpenPublicRoom
: isNavigationReady && hasAttemptedToOpenPublicRoom;
const shouldHideSplash =
shouldInit &&
(NativeModules.HybridAppModule ? splashScreenState === CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN && isAuthenticated : splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE);
(NativeModules.HybridAppModule
? splashScreenState === CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN && (isAuthenticated || !!hybridApp?.useNewDotSignInPage)
: splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE);

const initializeClient = () => {
if (!Visibility.isVisible()) {
Expand Down
7 changes: 3 additions & 4 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -443,15 +443,14 @@ const ONYXKEYS = {
/** Stores recently used currencies */
RECENTLY_USED_CURRENCIES: 'nvp_recentlyUsedCurrencies',

/** States whether we transitioned from OldDot to show only certain group of screens. It should be undefined on pure NewDot. */
IS_SINGLE_NEW_DOT_ENTRY: 'isSingleNewDotEntry',

/** Company cards custom names */
NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES: 'nvp_expensify_ccCustomNames',

/** The user's Concierge reportID */
CONCIERGE_REPORT_ID: 'conciergeReportID',

HYBRID_APP: 'hybridApp',

/** Collection Keys */
COLLECTION: {
DOWNLOAD: 'download_',
Expand Down Expand Up @@ -1010,11 +1009,11 @@ type OnyxValuesMapping = {
[ONYXKEYS.APPROVAL_WORKFLOW]: OnyxTypes.ApprovalWorkflowOnyx;
[ONYXKEYS.IMPORTED_SPREADSHEET]: OnyxTypes.ImportedSpreadsheet;
[ONYXKEYS.LAST_ROUTE]: string;
[ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY]: boolean | undefined;
[ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean;
[ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record<string, string>;
[ONYXKEYS.CONCIERGE_REPORT_ID]: string;
[ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING]: OnyxTypes.DismissedProductTraining;
[ONYXKEYS.HYBRID_APP]: OnyxTypes.HybridApp;
};
type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping;

Expand Down
33 changes: 21 additions & 12 deletions src/components/InitialURLContextProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, {createContext, useEffect, useMemo, useState} from 'react';
import React, {createContext, useEffect, useMemo, useRef, useState} from 'react';
import type {ReactNode} from 'react';
import {Linking} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import {signInAfterTransitionFromOldDot} from '@libs/actions/Session';
import {setupNewDotAfterTransitionFromOldDot} from '@libs/actions/Session';
import Navigation from '@navigation/Navigation';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import {useSplashScreenStateContext} from '@src/SplashScreenStateContext';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';

type InitialUrlContextType = {
initialURL: Route | undefined;
Expand All @@ -25,14 +26,19 @@ type InitialURLContextProviderProps = {
/** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */
url?: Route | ValueOf<typeof CONST.HYBRID_APP>;

hybridAppSettings?: string;

/** Children passed to the context provider */
children: ReactNode;
};

function InitialURLContextProvider({children, url}: InitialURLContextProviderProps) {
const [initialURL, setInitialURL] = useState<Route | undefined>();
function InitialURLContextProvider({children, url, hybridAppSettings}: InitialURLContextProviderProps) {
const [initialURL, setInitialURL] = useState<Route | ValueOf<typeof CONST.HYBRID_APP> | undefined>(url);
const [lastVisitedPath] = useOnyx(ONYXKEYS.LAST_VISITED_PATH);
const {splashScreenState, setSplashScreenState} = useSplashScreenStateContext();
const [tryNewDot, tryNewDotMetadata] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT);
// We use `setupCalled` ref to guarantee that `signInAfterTransitionFromOldDot` is called once.
const setupCalled = useRef(false);

useEffect(() => {
if (url !== CONST.HYBRID_APP.REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT) {
Expand All @@ -50,22 +56,25 @@ function InitialURLContextProvider({children, url}: InitialURLContextProviderPro
return;
}

if (url) {
signInAfterTransitionFromOldDot(url).then((route) => {
setInitialURL(route);
setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
});
if (url && hybridAppSettings) {
if (!isLoadingOnyxValue(tryNewDotMetadata) && !setupCalled.current) {
setupCalled.current = true;
setupNewDotAfterTransitionFromOldDot(url, hybridAppSettings, tryNewDot).then((route) => {
setInitialURL(route);
setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
});
}
return;
}
Linking.getInitialURL().then((initURL) => {
setInitialURL(initURL as Route);
});
}, [setSplashScreenState, url]);
}, [hybridAppSettings, setSplashScreenState, tryNewDot, tryNewDotMetadata, url]);

const initialUrlContext = useMemo(
() => ({
initialURL,
setInitialURL,
initialURL: initialURL === CONST.HYBRID_APP.REORDERING_REACT_NATIVE_ACTIVITY_TO_FRONT ? undefined : initialURL,
setInitialURL: setInitialURL as React.Dispatch<React.SetStateAction<Route | undefined>>,
}),
[initialURL],
);
Expand Down
2 changes: 1 addition & 1 deletion src/hooks/useOnboardingFlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ function useOnboardingFlowRouter() {

const [dismissedProductTraining, dismissedProductTrainingMetadata] = useOnyx(ONYXKEYS.NVP_DISMISSED_PRODUCT_TRAINING);

const [isSingleNewDotEntry, isSingleNewDotEntryMetadata] = useOnyx(ONYXKEYS.IS_SINGLE_NEW_DOT_ENTRY);
const [isSingleNewDotEntry, isSingleNewDotEntryMetadata] = useOnyx(ONYXKEYS.HYBRID_APP, {selector: (data) => data?.isSingleNewDotEntry});
const [allBetas, allBetasMetadata] = useOnyx(ONYXKEYS.BETAS);
useEffect(() => {
if (isLoadingOnyxValue(isOnboardingCompletedMetadata, tryNewDotdMetadata, dismissedProductTrainingMetadata, allBetasMetadata)) {
Expand Down
8 changes: 4 additions & 4 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@ const WRITE_COMMANDS = {
REQUEST_NEW_VALIDATE_CODE: 'RequestNewValidateCode',
SIGN_IN_WITH_APPLE: 'SignInWithApple',
SIGN_IN_WITH_GOOGLE: 'SignInWithGoogle',
SIGN_IN_USER: 'SigninUser',
SIGN_IN_USER_WITH_LINK: 'SigninUserWithLink',
SEARCH: 'Search',
REQUEST_UNLINK_VALIDATION_LINK: 'RequestUnlinkValidationLink',
Expand Down Expand Up @@ -307,7 +306,6 @@ const WRITE_COMMANDS = {
TRANSACTION_MERGE: 'Transaction_Merge',
RESOLVE_DUPLICATES: 'ResolveDuplicates',
UPDATE_SUBSCRIPTION_TYPE: 'UpdateSubscriptionType',
SIGN_UP_USER: 'SignUpUser',
UPDATE_SUBSCRIPTION_AUTO_RENEW: 'UpdateSubscriptionAutoRenew',
UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY: 'UpdateSubscriptionAddNewUsersAutomatically',
UPDATE_SUBSCRIPTION_SIZE: 'UpdateSubscriptionSize',
Expand Down Expand Up @@ -517,7 +515,6 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.REQUEST_NEW_VALIDATE_CODE]: Parameters.RequestNewValidateCodeParams;
[WRITE_COMMANDS.SIGN_IN_WITH_APPLE]: Parameters.BeginAppleSignInParams;
[WRITE_COMMANDS.SIGN_IN_WITH_GOOGLE]: Parameters.BeginGoogleSignInParams;
[WRITE_COMMANDS.SIGN_IN_USER]: SignInUserParams;
[WRITE_COMMANDS.SIGN_IN_USER_WITH_LINK]: Parameters.SignInUserWithLinkParams;
[WRITE_COMMANDS.REQUEST_UNLINK_VALIDATION_LINK]: Parameters.RequestUnlinkValidationLinkParams;
[WRITE_COMMANDS.UNLINK_LOGIN]: Parameters.UnlinkLoginParams;
Expand Down Expand Up @@ -758,7 +755,6 @@ type WriteCommandParameters = {
[WRITE_COMMANDS.TRANSACTION_MERGE]: Parameters.TransactionMergeParams;
[WRITE_COMMANDS.RESOLVE_DUPLICATES]: Parameters.ResolveDuplicatesParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_TYPE]: Parameters.UpdateSubscriptionTypeParams;
[WRITE_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_AUTO_RENEW]: Parameters.UpdateSubscriptionAutoRenewParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_ADD_NEW_USERS_AUTOMATICALLY]: Parameters.UpdateSubscriptionAddNewUsersAutomaticallyParams;
[WRITE_COMMANDS.UPDATE_SUBSCRIPTION_SIZE]: Parameters.UpdateSubscriptionSizeParams;
Expand Down Expand Up @@ -1033,6 +1029,8 @@ const SIDE_EFFECT_REQUEST_COMMANDS = {
DISCONNECT_AS_DELEGATE: 'DisconnectAsDelegate',
COMPLETE_HYBRID_APP_ONBOARDING: 'CompleteHybridAppOnboarding',
CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP: 'ConnectPolicyToQuickbooksDesktop',
SIGN_IN_USER: 'SigninUser',
SIGN_UP_USER: 'SignUpUser',

// PayMoneyRequestOnSearch only works online (pattern C) and we need to play the success sound only when the request is successful
PAY_MONEY_REQUEST_ON_SEARCH: 'PayMoneyRequestOnSearch',
Expand All @@ -1056,6 +1054,8 @@ type SideEffectRequestCommandParameters = {
[SIDE_EFFECT_REQUEST_COMMANDS.DISCONNECT_AS_DELEGATE]: EmptyObject;
[SIDE_EFFECT_REQUEST_COMMANDS.COMPLETE_HYBRID_APP_ONBOARDING]: EmptyObject;
[SIDE_EFFECT_REQUEST_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_DESKTOP]: Parameters.ConnectPolicyToQuickBooksDesktopParams;
[SIDE_EFFECT_REQUEST_COMMANDS.SIGN_IN_USER]: SignInUserParams;
[SIDE_EFFECT_REQUEST_COMMANDS.SIGN_UP_USER]: Parameters.SignUpUserParams;
[SIDE_EFFECT_REQUEST_COMMANDS.PAY_MONEY_REQUEST_ON_SEARCH]: Parameters.PayMoneyRequestOnSearchParams;
};

Expand Down
94 changes: 94 additions & 0 deletions src/libs/HybridApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {NativeModules} from 'react-native';
import Onyx from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Credentials, HybridApp, Session, TryNewDot} from '@src/types/onyx';
import * as HybridAppActions from './actions/HybridApp';
import Log from './Log';
import {getCurrentUserEmail} from './Network/NetworkStore';

let currentHybridApp: OnyxEntry<HybridApp>;
let currentTryNewDot: OnyxEntry<TryNewDot>;
let currentCredentials: OnyxEntry<Credentials>;

Onyx.connect({
key: ONYXKEYS.HYBRID_APP,
callback: (hybridApp) => {
handleChangeInHybridAppSignInFlow(hybridApp, currentTryNewDot, currentCredentials);
},
});

Onyx.connect({
key: ONYXKEYS.NVP_TRYNEWDOT,
callback: (tryNewDot) => {
handleChangeInHybridAppSignInFlow(currentHybridApp, tryNewDot, currentCredentials);
},
});

Onyx.connect({
key: ONYXKEYS.CREDENTIALS,
callback: (credentials) => {
currentCredentials = credentials;
handleChangeInHybridAppSignInFlow(currentHybridApp, currentTryNewDot, credentials);
},
});

let currentSession: OnyxEntry<Session>;
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (session: OnyxEntry<Session>) => {
if (!currentSession?.authToken && session?.authToken && currentHybridApp?.newDotSignInState === CONST.HYBRID_APP_SIGN_IN_STATE.STARTED) {
HybridAppActions.setNewDotSignInState(CONST.HYBRID_APP_SIGN_IN_STATE.FINISHED);
}
currentSession = session;
},
});

let activePolicyID: OnyxEntry<string>;
Onyx.connect({
key: ONYXKEYS.NVP_ACTIVE_POLICY_ID,
callback: (newActivePolicyID) => {
activePolicyID = newActivePolicyID;
},
});

function shouldUseOldApp(tryNewDot?: TryNewDot) {
return tryNewDot?.classicRedirect.dismissed === true;
}

function handleChangeInHybridAppSignInFlow(hybridApp: OnyxEntry<HybridApp>, tryNewDot: OnyxEntry<TryNewDot>, credentials: OnyxEntry<Credentials>) {
if (!NativeModules.HybridAppModule) {
return;
}

if (!hybridApp?.useNewDotSignInPage) {
currentHybridApp = hybridApp;
currentTryNewDot = tryNewDot;
return;
}

if (hybridApp?.newDotSignInState === CONST.HYBRID_APP_SIGN_IN_STATE.FINISHED && tryNewDot !== undefined && !!credentials?.autoGeneratedLogin && !!credentials?.autoGeneratedPassword) {
Log.info(`[HybridApp] Performing sign-in${shouldUseOldApp(tryNewDot) ? '' : ' (in background)'} on OldDot side`);
NativeModules.HybridAppModule.signInToOldDot(
credentials.autoGeneratedLogin,
credentials.autoGeneratedPassword,
currentSession?.authToken ?? '',
getCurrentUserEmail() ?? '',
activePolicyID ?? '',
);
HybridAppActions.setUseNewDotSignInPage(false).then(() => {
if (shouldUseOldApp(tryNewDot)) {
NativeModules.HybridAppModule.closeReactNativeApp(false, false);
} else {
Log.info('[HybridApp] The user should see NewDot. There is no need to block the user on the `SignInPage` until the sign-in process is completed on the OldDot side.');
HybridAppActions.setReadyToShowAuthScreens(true);
}
});
}

currentHybridApp = hybridApp;
currentTryNewDot = tryNewDot;
}

export default {shouldUseOldApp};
19 changes: 8 additions & 11 deletions src/libs/Navigation/AppNavigator/AuthScreens.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {findFocusedRoute} from '@react-navigation/native';
import React, {memo, useEffect, useRef, useState} from 'react';
import {NativeModules, View} from 'react-native';
import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import Onyx, {withOnyx} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
Expand Down Expand Up @@ -280,16 +280,13 @@ function AuthScreens({session, lastOpenedPublicRoomID, initialLastUpdateIDApplie
PusherConnectionManager.init();
initializePusher();

// In Hybrid App we decide to call one of those method when booting ND and we don't want to duplicate calls
if (!NativeModules.HybridAppModule) {
// If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app
// or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp().
if (SessionUtils.didUserLogInDuringSession()) {
App.openApp();
} else {
Log.info('[AuthScreens] Sending ReconnectApp');
App.reconnectApp(initialLastUpdateIDAppliedToClient);
}
// If we are on this screen then we are "logged in", but the user might not have "just logged in". They could be reopening the app
// or returning from background. If so, we'll assume they have some app data already and we can call reconnectApp() instead of openApp().
if (SessionUtils.didUserLogInDuringSession()) {
App.openApp();
} else {
Log.info('[AuthScreens] Sending ReconnectApp');
App.reconnectApp(initialLastUpdateIDAppliedToClient);
}

PriorityMode.autoSwitchToFocusMode();
Expand Down
4 changes: 1 addition & 3 deletions src/libs/Navigation/AppNavigator/PublicScreens.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import React from 'react';
import {NativeModules} from 'react-native';
import createPlatformStackNavigator from '@libs/Navigation/PlatformStackNavigation/createPlatformStackNavigator';
import type {PublicScreensParamList} from '@navigation/types';
import ConnectionCompletePage from '@pages/ConnectionCompletePage';
import SessionExpiredPage from '@pages/ErrorPage/SessionExpiredPage';
import LogInWithShortLivedAuthTokenPage from '@pages/LogInWithShortLivedAuthTokenPage';
import AppleSignInDesktopPage from '@pages/signin/AppleSignInDesktopPage';
import GoogleSignInDesktopPage from '@pages/signin/GoogleSignInDesktopPage';
Expand All @@ -23,7 +21,7 @@ function PublicScreens() {
{/* The structure for the HOME route has to be the same in public and auth screens. That's why the name for SignInPage is BOTTOM_TAB_NAVIGATOR. */}
<RootStack.Screen
name={NAVIGATORS.BOTTOM_TAB_NAVIGATOR}
component={NativeModules.HybridAppModule ? SessionExpiredPage : SignInPage}
component={SignInPage}
/>
<RootStack.Screen
name={SCREENS.TRANSITION_BETWEEN_APPS}
Expand Down
Loading

0 comments on commit 74d0c59

Please sign in to comment.