Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[HybridApp] Implement POC of NewDot SignIn on HybridApp #51257

Draft
wants to merge 57 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
5eaa292
Implement POC of NewDot login page on HybridApp
mateuuszzzzz Oct 22, 2024
404047f
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
war-in Oct 28, 2024
f81ec86
fix android crash
war-in Oct 29, 2024
290748d
add HybridApp onyx key
war-in Oct 29, 2024
05c4573
wip - sending native events
war-in Oct 31, 2024
b6e14aa
Merge branch 'refs/heads/main' into war-in/unmock-signing-in
war-in Oct 31, 2024
bb05ef9
js logic
war-in Oct 31, 2024
94ebb8f
fix switching to new experience
war-in Oct 31, 2024
a9ff586
block switching when still signing in to OD
war-in Oct 31, 2024
ec6315e
reset onyx values on start
war-in Oct 31, 2024
db0d806
Merge branch 'refs/heads/main' into make-new-dot-signin-default-for-h…
war-in Oct 31, 2024
e1562bb
preserve HYBRID_APP onyx key when signing out
war-in Oct 31, 2024
efde805
fix
war-in Oct 31, 2024
a1b9e1a
move part of resetting logic
war-in Oct 31, 2024
3ad1b62
move useNewDotSignInPage to HybridApp key
war-in Nov 4, 2024
f9b0e5a
fix lint & typecheck
war-in Nov 4, 2024
377b686
correctly set onyx props when switching from OD
war-in Nov 4, 2024
90f7c0f
don't set HybridApp key in onyx (merge it)
war-in Nov 4, 2024
4b3e539
send error message on failed OD login
war-in Nov 4, 2024
026ac7b
add useOldDot const
war-in Nov 5, 2024
624be42
Merge branch 'main' into war-in/unmock-signing-in
war-in Nov 6, 2024
44dffc8
Merge branch 'war-in/unmock-signing-in' into make-new-dot-signin-defa…
war-in Nov 6, 2024
d976148
Bootsplash logic in hybridapp (#132)
jnowakow Nov 7, 2024
11e444d
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
war-in Nov 12, 2024
a78141d
fix clearing onyx
war-in Nov 13, 2024
deaba6f
[HybridApp] Do not use query params in AppProp URL (#125)
mateuuszzzzz Nov 13, 2024
f2e3bb4
Handle OD sing-in errors (#136)
war-in Nov 13, 2024
8859b6c
add disabled to button
war-in Nov 13, 2024
a3436bc
move sign in state to HybridApp key
war-in Nov 13, 2024
703c31d
add loading indicator and error modal
war-in Nov 14, 2024
1f6a0e1
move sign in logic to HybridApp.ts
war-in Nov 14, 2024
63a3de8
fix lint
war-in Nov 14, 2024
9fdeebb
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
mateuuszzzzz Nov 18, 2024
4bc9f90
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
mateuuszzzzz Nov 20, 2024
1a99fd1
Prevent closing NewDot on first transition with button
mateuuszzzzz Nov 20, 2024
0e601bd
use "tryNewDot" value to determine which app should be opened
war-in Nov 21, 2024
1c0c219
first step to fix deeplinks
war-in Nov 21, 2024
c848cfd
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
war-in Nov 27, 2024
4170968
improve openApp flow
war-in Nov 27, 2024
50c3fed
[HybridApp] Clean code (#143)
mateuuszzzzz Nov 27, 2024
fb82b65
[HybridApp] Refactor `SignInPage` PR v2 (#144)
mateuuszzzzz Nov 28, 2024
ecb93e2
fix part of signup flow
war-in Nov 27, 2024
6f926fe
[SignIn Page] New user flow (#145)
war-in Nov 28, 2024
f540207
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
war-in Nov 28, 2024
5aae4be
Merge remote-tracking branch 'origin/make-new-dot-signin-default-for-…
war-in Nov 28, 2024
90c0954
fix isSingleNewDotEntry logic (travel feature)
war-in Dec 2, 2024
13180c2
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
war-in Dec 2, 2024
ddb9aef
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
war-in Dec 5, 2024
9cfbaf5
Rename argument
mateuuszzzzz Dec 5, 2024
85eddb2
Change function name
mateuuszzzzz Dec 5, 2024
bddba03
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
war-in Dec 9, 2024
68ed516
Dont sign in in old dot (#150)
war-in Dec 9, 2024
9450d21
clear onyx but leave same data as when signing out
war-in Dec 9, 2024
62b861c
Improve data fetching on OldDot side
mateuuszzzzz Dec 10, 2024
8ea545c
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
mateuuszzzzz Dec 11, 2024
837cbfe
Merge branch 'main' into make-new-dot-signin-default-for-hybridapp
war-in Dec 13, 2024
892dec2
Add named params in HybridAppModule methods (#153)
war-in Dec 13, 2024
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
5 changes: 4 additions & 1 deletion src/Expensify.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,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 [useNewDotSignInPage] = useOnyx(ONYXKEYS.USE_NEWDOT_SIGN_IN_PAGE);

useEffect(() => {
if (!account?.needsTwoFactorAuthSetup || account.requiresTwoFactorAuth) {
Expand All @@ -119,7 +120,9 @@ function Expensify() {
const shouldInit = 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 || useNewDotSignInPage)
: splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE);

const initializeClient = () => {
if (!Visibility.isVisible()) {
Expand Down
7 changes: 7 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,11 @@ const ONYXKEYS = {
/** Company cards custom names */
NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES: 'nvp_expensify_ccCustomNames',

/** Stores the information if HybridApp uses NewDot's sign in flow */
USE_NEWDOT_SIGN_IN_PAGE: 'useNewDotSignInPage',

HYBRID_APP: 'hybridApp',

/** Collection Keys */
COLLECTION: {
DOWNLOAD: 'download_',
Expand Down Expand Up @@ -1012,6 +1017,8 @@ type OnyxValuesMapping = {
[ONYXKEYS.IS_USING_IMPORTED_STATE]: boolean;
[ONYXKEYS.SHOULD_SHOW_SAVED_SEARCH_RENAME_TOOLTIP]: boolean;
[ONYXKEYS.NVP_EXPENSIFY_COMPANY_CARDS_CUSTOM_NAMES]: Record<string, string>;
[ONYXKEYS.USE_NEWDOT_SIGN_IN_PAGE]: boolean;
[ONYXKEYS.HYBRID_APP]: OnyxTypes.HybridApp;
};
type OnyxValues = OnyxValuesMapping & OnyxCollectionValuesMapping & OnyxFormValuesMapping & OnyxFormDraftValuesMapping;

Expand Down
7 changes: 4 additions & 3 deletions src/components/InitialURLContextProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@ function InitialURLContextProvider({children, url}: InitialURLContextProviderPro

useEffect(() => {
if (url) {
const route = signInAfterTransitionFromOldDot(url);
setInitialURL(route);
setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
signInAfterTransitionFromOldDot(url).then((route) => {
setInitialURL(route);
setSplashScreenState(CONST.BOOT_SPLASH_STATE.READY_TO_BE_HIDDEN);
});
return;
}
Linking.getInitialURL().then((initURL) => {
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 {createStackNavigator} from '@react-navigation/stack';
import React from 'react';
import {NativeModules} from 'react-native';
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 @@ -24,7 +22,7 @@ function PublicScreens() {
<RootStack.Screen
name={NAVIGATORS.BOTTOM_TAB_NAVIGATOR}
options={defaultScreenOptions}
component={NativeModules.HybridAppModule ? SessionExpiredPage : SignInPage}
component={SignInPage}
/>
<RootStack.Screen
name={SCREENS.TRANSITION_BETWEEN_APPS}
Expand Down
19 changes: 15 additions & 4 deletions src/libs/Navigation/AppNavigator/index.native.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import React, {memo, useContext, useEffect} from 'react';
import React, {memo, useContext, useEffect, useMemo} from 'react';
import {NativeModules} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import {InitialURLContext} from '@components/InitialURLContextProvider';
import Navigation from '@libs/Navigation/Navigation';
import ONYXKEYS from '@src/ONYXKEYS';
import type ReactComponentModule from '@src/types/utils/ReactComponentModule';

type AppNavigatorProps = {
Expand All @@ -11,19 +13,28 @@ type AppNavigatorProps = {

function AppNavigator({authenticated}: AppNavigatorProps) {
const {initialURL, setInitialURL} = useContext(InitialURLContext);
const [useNewDotLoginPage] = useOnyx(ONYXKEYS.USE_NEWDOT_SIGN_IN_PAGE);

const shouldShowAuthScreens = useMemo(() => {
if (!NativeModules.HybridAppModule) {
return authenticated;
}

return useNewDotLoginPage === false && authenticated;
}, [authenticated, useNewDotLoginPage]);
mateuuszzzzz marked this conversation as resolved.
Show resolved Hide resolved

useEffect(() => {
if (!NativeModules.HybridAppModule || !initialURL) {
if (!NativeModules.HybridAppModule || !initialURL || !shouldShowAuthScreens) {
return;
}

Navigation.isNavigationReady().then(() => {
Navigation.navigate(Navigation.parseHybridAppUrl(initialURL));
setInitialURL(undefined);
});
}, [initialURL, setInitialURL]);
}, [initialURL, setInitialURL, shouldShowAuthScreens]);

if (authenticated) {
if (shouldShowAuthScreens) {
const AuthScreens = require<ReactComponentModule>('./AuthScreens').default;

// These are the protected screens and only accessible when an authToken is present
Expand Down
63 changes: 42 additions & 21 deletions src/libs/actions/Session/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,7 @@ function signOutAndRedirectToSignIn(shouldResetToHome?: boolean, shouldStashSess
if (!isAnonymousUser()) {
// In the HybridApp, we want the Old Dot to handle the sign out process
if (NativeModules.HybridAppModule && killHybridApp) {
NativeModules.HybridAppModule.closeReactNativeApp(true, false);
return;
NativeModules.HybridAppModule.signOutFromOldDot();
}
// We'll only call signOut if we're not stashing the session and this is not a supportal session,
// otherwise we'll call the API to invalidate the autogenerated credentials used for infinite
Expand Down Expand Up @@ -482,29 +481,51 @@ function signUpUser() {

function signInAfterTransitionFromOldDot(transitionURL: string) {
const [route, queryParams] = transitionURL.split('?');
const queryParamsObject = queryParams
? Object.fromEntries(
queryParams.split('&').map((param) => {
const [key, value] = param.split('=');
return [key, value];
}),
)
: {};

const {useNewDotSignInPage, isSingleNewDotEntry} = queryParamsObject;

const clearOnyxBeforeSignIn = () => {
if (useNewDotSignInPage !== 'true') {
return Promise.resolve();
}

const {email, authToken, encryptedAuthToken, accountID, autoGeneratedLogin, autoGeneratedPassword, clearOnyxOnStart, completedHybridAppOnboarding} = Object.fromEntries(
queryParams.split('&').map((param) => {
const [key, value] = param.split('=');
return [key, value];
}),
);

const setSessionDataAndOpenApp = () => {
Onyx.multiSet({
[ONYXKEYS.SESSION]: {email, authToken, encryptedAuthToken: decodeURIComponent(encryptedAuthToken), accountID: Number(accountID)},
[ONYXKEYS.CREDENTIALS]: {autoGeneratedLogin, autoGeneratedPassword},
[ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {completedHybridAppOnboarding: completedHybridAppOnboarding === 'true'}},
}).then(App.openApp);
return Onyx.clear();
};

if (clearOnyxOnStart === 'true') {
Onyx.clear(KEYS_TO_PRESERVE).then(setSessionDataAndOpenApp);
} else {
setSessionDataAndOpenApp();
}
const initAppAfterTransition = () => {
if (useNewDotSignInPage === 'true') {
return Promise.resolve();
}

return App.openApp();
};

const setSessionDataAndOpenApp = new Promise<Route>((resolve) => {
clearOnyxBeforeSignIn()
.then(() =>
Onyx.multiSet({
[ONYXKEYS.USE_NEWDOT_SIGN_IN_PAGE]: useNewDotSignInPage === 'true',
[ONYXKEYS.NVP_TRYNEWDOT]: {classicRedirect: {dismissed: 'true'}}, // This data is mocked and should be returned by BeginSignUp/SignInUser API commands
}),
)
.then(initAppAfterTransition)
.catch((error) => {
Log.hmmm('[HybridApp] Initialization of HybridApp has failed. Forcing transition', {error});
})
.finally(() => {
resolve(`${route}?singleNewDotEntry=${isSingleNewDotEntry}` as Route);
});
});

return route as Route;
return setSessionDataAndOpenApp;
}

/**
Expand Down
34 changes: 30 additions & 4 deletions src/pages/signin/ValidateCodeForm/BaseValidateCodeForm.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useIsFocused} from '@react-navigation/native';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {ForwardedRef} from 'react';
import {View} from 'react-native';
import {NativeModules, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import SafariFormWrapper from '@components/Form/SafariFormWrapper';
Expand Down Expand Up @@ -30,6 +30,7 @@ import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue';
import type ValidateCodeFormProps from './types';

type BaseValidateCodeFormProps = WithToggleVisibilityViewProps &
Expand Down Expand Up @@ -61,6 +62,7 @@ function BaseValidateCodeForm({autoComplete, isUsingRecoveryCode, setIsUsingReco
const [timeRemaining, setTimeRemaining] = useState(CONST.REQUEST_CODE_DELAY as number);
const [recoveryCode, setRecoveryCode] = useState('');
const [needToClearError, setNeedToClearError] = useState<boolean>(!!account?.errors);
const [tryNewDot, tryNewDotMetadata] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT);

const prevRequiresTwoFactorAuth = usePrevious(account?.requiresTwoFactorAuth);
const prevValidateCode = usePrevious(credentials?.validateCode);
Expand All @@ -74,6 +76,30 @@ function BaseValidateCodeForm({autoComplete, isUsingRecoveryCode, setIsUsingReco
const shouldDisableResendValidateCode = isOffline ?? account?.isLoading;
const isValidateCodeFormSubmitting = AccountUtils.isValidateCodeFormSubmitting(account);

const isLoading = useMemo(() => {
if (!NativeModules.HybridAppModule || !session?.authToken) {
return isValidateCodeFormSubmitting;
}

if (isLoadingOnyxValue(tryNewDotMetadata)) {
return true;
}

return (tryNewDot?.classicRedirect.dismissed === true || tryNewDot?.classicRedirect.dismissed === 'true') && !!session.authToken;
}, [isValidateCodeFormSubmitting, session?.authToken, tryNewDot, tryNewDotMetadata]);

useEffect(() => {
if (!NativeModules.HybridAppModule || isValidateCodeFormSubmitting || !session?.authToken) {
return;
}

if (tryNewDot?.classicRedirect.dismissed === true || tryNewDot?.classicRedirect.dismissed === 'true') {
if (credentials?.autoGeneratedLogin && credentials?.autoGeneratedPassword) {
NativeModules.HybridAppModule.signInToOldDot(credentials?.autoGeneratedLogin, credentials?.autoGeneratedPassword);
}
}
}, [credentials?.autoGeneratedLogin, credentials?.autoGeneratedPassword, isValidateCodeFormSubmitting, session?.authToken, tryNewDot?.classicRedirect.dismissed]);

useEffect(() => {
if (!(inputValidateCodeRef.current && hasError && (session?.autoAuthState === CONST.AUTO_AUTH_STATE.FAILED || account?.isLoading))) {
return;
Expand Down Expand Up @@ -335,7 +361,7 @@ function BaseValidateCodeForm({autoComplete, isUsingRecoveryCode, setIsUsingReco
style={[styles.mt2]}
onPress={switchBetween2faAndRecoveryCode}
hoverDimmingValue={1}
disabled={isValidateCodeFormSubmitting}
disabled={isLoading}
role={CONST.ROLE.BUTTON}
accessibilityLabel={isUsingRecoveryCode ? translate('recoveryCodeForm.use2fa') : translate('recoveryCodeForm.useRecoveryCode')}
>
Expand Down Expand Up @@ -394,7 +420,7 @@ function BaseValidateCodeForm({autoComplete, isUsingRecoveryCode, setIsUsingReco
large
style={[styles.mv3]}
text={translate('common.signIn')}
isLoading={isValidateCodeFormSubmitting}
isLoading={isLoading}
onPress={validateAndSubmitForm}
/>
<ChangeExpensifyLoginLink onPress={clearSignInData} />
Expand Down
2 changes: 2 additions & 0 deletions src/types/modules/react-native.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import type StartupTimer from '@libs/StartupTimer/types';
type HybridAppModule = {
closeReactNativeApp: (shouldSignOut: boolean, shouldSetNVP: boolean) => void;
completeOnboarding: (status: boolean) => void;
signInToOldDot: (autoGeneratedLogin: string, autoGeneratedPassword: string) => void;
signOutFromOldDot: () => void;
exitApp: () => void;
};

Expand Down
7 changes: 7 additions & 0 deletions src/types/onyx/HybridApp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/** */
type HybridApp = {
/** */
isSigningIn?: boolean;
};

export default HybridApp;
2 changes: 2 additions & 0 deletions src/types/onyx/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import type ExpensifyCardSettings from './ExpensifyCardSettings';
import type FrequentlyUsedEmoji from './FrequentlyUsedEmoji';
import type {FundList} from './Fund';
import type Fund from './Fund';
import type HybridApp from './HybridApp';
import type ImportedSpreadsheet from './ImportedSpreadsheet';
import type IntroSelected from './IntroSelected';
import type InvitedEmailsToAccountIDs from './InvitedEmailsToAccountIDs';
Expand Down Expand Up @@ -235,4 +236,5 @@ export type {
RecentSearchItem,
ImportedSpreadsheet,
ValidateMagicCodeAction,
HybridApp,
};
Loading