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] Share auth token in HybridApp #48007

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
7c84a1a
Get more necessary session data from OldDot
mateuuszzzzz Aug 26, 2024
140a84f
Get rid of custom BootSplash logic for HybridApp
mateuuszzzzz Aug 26, 2024
7f1c4e9
Remove invalid prop from HybridAppMiddleware
mateuuszzzzz Aug 26, 2024
3173707
Clean up code
mateuuszzzzz Aug 30, 2024
a9e3fef
clear onyx on new sign in
war-in Aug 30, 2024
8dbf093
Refactor code and temporary do not propagate encryptedAuthToken
mateuuszzzzz Aug 30, 2024
fd8d79e
pass shouldClearOnyxOnStart in url
war-in Aug 30, 2024
938b2b7
Change HybridApp BootSplash condition
mateuuszzzzz Aug 30, 2024
9641969
Merge branch 'main' into share-token-in-hybridapp
mateuuszzzzz Aug 30, 2024
09ac532
fix signout button
war-in Aug 30, 2024
f998be4
Merge branch 'war-in/clear-onyx-on-new-sign-in' into share-token-in-h…
mateuuszzzzz Aug 30, 2024
e68f007
Use App.openApp on transition
mateuuszzzzz Aug 30, 2024
4c305a6
Fix openApp
mateuuszzzzz Aug 30, 2024
3acf03e
Add React import
mateuuszzzzz Aug 30, 2024
46833c2
Merge branch 'refs/heads/main' into share-token-in-hybridapp
war-in Sep 2, 2024
c111894
Improve cache clearing logic
mateuuszzzzz Sep 2, 2024
1e82645
add splashScreenStateContext
war-in Sep 2, 2024
a3aa755
fix lint
war-in Sep 2, 2024
320de61
move ready to be hidden to InitialURLContextProvider
war-in Sep 2, 2024
01a93f0
opened -> visible
war-in Sep 2, 2024
3b5e1eb
remove unused `useExitTo` hook
war-in Sep 2, 2024
c47905b
Allow to navigate to routes different than transition
mateuuszzzzz Sep 2, 2024
2b772d0
Do not reset home on SignOut
mateuuszzzzz Sep 2, 2024
b052157
fix initialUrl while OD -> ND
war-in Sep 2, 2024
2e955e9
reintroduce onboarding
war-in Sep 3, 2024
620150b
Fallback to SessionExpiredPage on HybridApp instead of SignUp page
mateuuszzzzz Sep 3, 2024
94f6727
Remove unused code
mateuuszzzzz Sep 3, 2024
1b1b474
Merge branch 'main' into share-token-in-hybridapp
mateuuszzzzz Sep 3, 2024
af4aabd
remove getVisibilityStatus
war-in Sep 3, 2024
ca8bfac
rename BOOT_SPLASH_STATE values
war-in Sep 4, 2024
9d43159
reset initial url on return to OD
war-in Sep 4, 2024
f650f9b
Change naming conventions
mateuuszzzzz Sep 4, 2024
6ecbb5e
Redact sensitive data logs
mateuuszzzzz Sep 4, 2024
1aab4f0
Improve redacting
mateuuszzzzz Sep 4, 2024
31085f9
Revert "Improve redacting"
war-in Sep 4, 2024
0e41e99
Revert "Redact sensitive data logs"
war-in Sep 4, 2024
f3d1d7f
fix patch
war-in Sep 4, 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
2 changes: 0 additions & 2 deletions __mocks__/react-native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ jest.doMock('react-native', () => {
type ReactNativeMock = typeof ReactNative & {
NativeModules: typeof ReactNative.NativeModules & {
BootSplash: {
getVisibilityStatus: typeof BootSplash.getVisibilityStatus;
hide: typeof BootSplash.hide;
logoSizeRatio: number;
navigationBarHeight: number;
Expand All @@ -46,7 +45,6 @@ jest.doMock('react-native', () => {
NativeModules: {
...ReactNative.NativeModules,
BootSplash: {
getVisibilityStatus: jest.fn(),
hide: jest.fn(),
logoSizeRatio: 1,
navigationBarHeight: 0,
Expand Down
40 changes: 40 additions & 0 deletions patches/react-native+0.75.2+017+redactAppParameters.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
diff --git a/node_modules/react-native/Libraries/ReactNative/AppRegistry.js b/node_modules/react-native/Libraries/ReactNative/AppRegistry.js
index 68bd389..be9b5bf 100644
--- a/node_modules/react-native/Libraries/ReactNative/AppRegistry.js
+++ b/node_modules/react-native/Libraries/ReactNative/AppRegistry.js
@@ -232,12 +232,34 @@ const AppRegistry = {
appParameters: Object,
displayMode?: number,
): void {
+ const redactAppParameters = (parameters) => {
+ const initialProps = parameters['initialProps'];
+ const url = initialProps['url'];
+
+ if(!url) {
+ return parameters;
+ }
+
+ const sensitiveParams = ['authToken', 'autoGeneratedPassword', 'autoGeneratedLogin'];
+ const [urlBase, queryString] = url.split('?');
+
+ if (!queryString) {
+ return parameters;
+ }
+
+ const redactedSearchParams = queryString.split('&').map((param) => {
+ const [key, value] = param.split('=');
+ return `${key}=${sensitiveParams.includes(key) ? '<REDACTED>' : value}`
+ });
+ return {...parameters, initialProps: {...initialProps, url: `${urlBase}?${redactedSearchParams.join('&')}`}};
+ }
+
if (appKey !== 'LogBox') {
const msg =
'Updating props for Surface "' +
appKey +
'" with ' +
- JSON.stringify(appParameters);
+ JSON.stringify(redactAppParameters(appParameters));
infoLog(msg);
BugReporting.addSource(
'AppRegistry.setSurfaceProps' + runCount++,
85 changes: 44 additions & 41 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import {ReportIDsContextProvider} from './hooks/useReportIDs';
import OnyxUpdateManager from './libs/actions/OnyxUpdateManager';
import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext';
import type {Route} from './ROUTES';
import {SplashScreenStateContextProvider} from './SplashScreenStateContext';

type AppProps = {
/** URL passed to our top-level React Native component by HybridApp. Will always be undefined in "pure" NewDot builds. */
Expand All @@ -64,47 +65,49 @@ function App({url}: AppProps) {

return (
<StrictModeWrapper>
<InitialURLContextProvider url={url}>
<GestureHandlerRootView style={fill}>
<ComposeProviders
components={[
OnyxProvider,
ThemeProvider,
ThemeStylesProvider,
ThemeIllustrationsProvider,
SafeAreaProvider,
PortalProvider,
SafeArea,
LocaleContextProvider,
HTMLEngineProvider,
WindowDimensionsProvider,
KeyboardStateProvider,
PopoverContextProvider,
CurrentReportIDContextProvider,
ScrollOffsetContextProvider,
ReportAttachmentsProvider,
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActiveWorkspaceContextProvider,
ReportIDsContextProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
]}
>
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
</ComposeProviders>
</GestureHandlerRootView>
</InitialURLContextProvider>
<SplashScreenStateContextProvider>
<InitialURLContextProvider url={url}>
<GestureHandlerRootView style={fill}>
<ComposeProviders
components={[
OnyxProvider,
ThemeProvider,
ThemeStylesProvider,
ThemeIllustrationsProvider,
SafeAreaProvider,
PortalProvider,
SafeArea,
LocaleContextProvider,
HTMLEngineProvider,
WindowDimensionsProvider,
KeyboardStateProvider,
PopoverContextProvider,
CurrentReportIDContextProvider,
ScrollOffsetContextProvider,
ReportAttachmentsProvider,
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActiveWorkspaceContextProvider,
ReportIDsContextProvider,
PlaybackContextProvider,
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
KeyboardProvider,
]}
>
<CustomStatusBarAndBackground />
<ErrorBoundary errorMessage="NewExpensify crash caught by error boundary">
<ColorSchemeWrapper>
<Expensify />
</ColorSchemeWrapper>
</ErrorBoundary>
</ComposeProviders>
</GestureHandlerRootView>
</InitialURLContextProvider>
</SplashScreenStateContextProvider>
</StrictModeWrapper>
);
}
Expand Down
6 changes: 6 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5494,6 +5494,12 @@ const CONST = {
},
},

BOOT_SPLASH_STATE: {
VISIBLE: 'visible',
READY_TO_BE_HIDDEN: 'readyToBeHidden',
HIDDEN: `hidden`,
},

CSV_IMPORT_COLUMNS: {
EMAIL: 'email',
NAME: 'name',
Expand Down
94 changes: 42 additions & 52 deletions src/Expensify.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {Audio} from 'expo-av';
import React, {useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import React, {useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState} from 'react';
import type {NativeEventSubscription} from 'react-native';
import {AppState, Linking, NativeModules, Platform} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
Expand All @@ -20,8 +20,8 @@ import {updateLastRoute} from './libs/actions/App';
import * as EmojiPickerAction from './libs/actions/EmojiPickerAction';
import * as Report from './libs/actions/Report';
import * as User from './libs/actions/User';
import {handleHybridAppOnboarding} from './libs/actions/Welcome';
import * as ActiveClientManager from './libs/ActiveClientManager';
import BootSplash from './libs/BootSplash';
import FS from './libs/Fullstory';
import * as Growl from './libs/Growl';
import Log from './libs/Log';
Expand All @@ -42,6 +42,7 @@ import PopoverReportActionContextMenu from './pages/home/report/ContextMenu/Popo
import * as ReportActionContextMenu from './pages/home/report/ContextMenu/ReportActionContextMenu';
import type {Route} from './ROUTES';
import ROUTES from './ROUTES';
import SplashScreenStateContext from './SplashScreenStateContext';
import type {ScreenShareRequest} from './types/onyx';

Onyx.registerLogger(({level, message}) => {
Expand Down Expand Up @@ -80,13 +81,6 @@ type ExpensifyOnyxProps = {

type ExpensifyProps = ExpensifyOnyxProps;

// HybridApp needs access to SetStateAction in order to properly hide SplashScreen when React Native was booted before.
type SplashScreenHiddenContextType = {isSplashHidden?: boolean; setIsSplashHidden: React.Dispatch<React.SetStateAction<boolean>>};

const SplashScreenHiddenContext = React.createContext<SplashScreenHiddenContextType>({
setIsSplashHidden: () => {},
});

function Expensify({
isCheckingPublicRoom = true,
updateAvailable,
Expand All @@ -99,12 +93,13 @@ function Expensify({
const appStateChangeListener = useRef<NativeEventSubscription | null>(null);
const [isNavigationReady, setIsNavigationReady] = useState(false);
const [isOnyxMigrated, setIsOnyxMigrated] = useState(false);
const [isSplashHidden, setIsSplashHidden] = useState(false);
const {splashScreenState, setSplashScreenState} = useContext(SplashScreenStateContext);
const [hasAttemptedToOpenPublicRoom, setAttemptedToOpenPublicRoom] = useState(false);
const {translate} = useLocalize();
const [account] = useOnyx(ONYXKEYS.ACCOUNT);
const [session] = useOnyx(ONYXKEYS.SESSION);
const [lastRoute] = useOnyx(ONYXKEYS.LAST_ROUTE);
const [tryNewDotData] = useOnyx(ONYXKEYS.NVP_TRYNEWDOT);
const [shouldShowRequire2FAModal, setShouldShowRequire2FAModal] = useState(false);

useEffect(() => {
Expand All @@ -123,11 +118,21 @@ function Expensify({
setAttemptedToOpenPublicRoom(true);
}, [isCheckingPublicRoom]);

useEffect(() => {
if (splashScreenState !== CONST.BOOT_SPLASH_STATE.HIDDEN || tryNewDotData === undefined) {
return;
}

handleHybridAppOnboarding();
}, [splashScreenState, tryNewDotData]);

const isAuthenticated = useMemo(() => !!(session?.authToken ?? null), [session]);
const autoAuthState = useMemo(() => session?.autoAuthState ?? '', [session]);

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

const initializeClient = () => {
if (!Visibility.isVisible()) {
Expand All @@ -145,17 +150,9 @@ function Expensify({
}, []);

const onSplashHide = useCallback(() => {
setIsSplashHidden(true);
setSplashScreenState(CONST.BOOT_SPLASH_STATE.HIDDEN);
Performance.markEnd(CONST.TIMING.SIDEBAR_LOADED);
}, []);

const contextValue = useMemo(
() => ({
isSplashHidden,
setIsSplashHidden,
}),
[isSplashHidden, setIsSplashHidden],
);
}, [setSplashScreenState]);

useLayoutEffect(() => {
// Initialize this client as being an active client
Expand All @@ -177,24 +174,22 @@ function Expensify({

useEffect(() => {
setTimeout(() => {
BootSplash.getVisibilityStatus().then((status) => {
const appState = AppState.currentState;
Log.info('[BootSplash] splash screen status', false, {appState, status});

if (status === 'visible') {
const propsToLog: Omit<ExpensifyProps & {isAuthenticated: boolean}, 'children' | 'session'> = {
isCheckingPublicRoom,
updateRequired,
updateAvailable,
isSidebarLoaded,
screenShareRequest,
focusModeNotification,
isAuthenticated,
lastVisitedPath,
};
Log.alert('[BootSplash] splash screen is still visible', {propsToLog}, false);
}
});
const appState = AppState.currentState;
Log.info('[BootSplash] splash screen status', false, {appState, splashScreenState});

if (splashScreenState === CONST.BOOT_SPLASH_STATE.VISIBLE) {
const propsToLog: Omit<ExpensifyProps & {isAuthenticated: boolean}, 'children' | 'session'> = {
isCheckingPublicRoom,
updateRequired,
updateAvailable,
isSidebarLoaded,
screenShareRequest,
focusModeNotification,
isAuthenticated,
lastVisitedPath,
};
Log.alert('[BootSplash] splash screen is still visible', {propsToLog}, false);
}
}, 30 * 1000);

// This timer is set in the native layer when launching the app and we stop it here so we can measure how long
Expand Down Expand Up @@ -304,18 +299,15 @@ function Expensify({

<AppleAuthWrapper />
{hasAttemptedToOpenPublicRoom && (
<SplashScreenHiddenContext.Provider value={contextValue}>
<NavigationRoot
onReady={setNavigationReady}
authenticated={isAuthenticated}
lastVisitedPath={lastVisitedPath as Route}
initialUrl={initialUrl}
shouldShowRequire2FAModal={shouldShowRequire2FAModal}
/>
</SplashScreenHiddenContext.Provider>
<NavigationRoot
onReady={setNavigationReady}
authenticated={isAuthenticated}
lastVisitedPath={lastVisitedPath as Route}
initialUrl={initialUrl}
shouldShowRequire2FAModal={shouldShowRequire2FAModal}
/>
)}
{/* HybridApp has own middleware to hide SplashScreen */}
{!NativeModules.HybridAppModule && shouldHideSplash && <SplashScreenHider onHide={onSplashHide} />}
{shouldHideSplash && <SplashScreenHider onHide={onSplashHide} />}
</DeeplinkWrapper>
);
}
Expand Down Expand Up @@ -349,5 +341,3 @@ export default withOnyx<ExpensifyProps, ExpensifyOnyxProps>({
key: ONYXKEYS.LAST_VISITED_PATH,
},
})(Expensify);

export {SplashScreenHiddenContext};
34 changes: 34 additions & 0 deletions src/SplashScreenStateContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, {useContext, useMemo, useState} from 'react';
import type {ValueOf} from 'type-fest';
import CONST from './CONST';
import type ChildrenProps from './types/utils/ChildrenProps';

type SplashScreenStateContextType = {
splashScreenState: ValueOf<typeof CONST.BOOT_SPLASH_STATE>;
setSplashScreenState: React.Dispatch<React.SetStateAction<ValueOf<typeof CONST.BOOT_SPLASH_STATE>>>;
};

const SplashScreenStateContext = React.createContext<SplashScreenStateContextType>({
splashScreenState: CONST.BOOT_SPLASH_STATE.VISIBLE,
setSplashScreenState: () => {},
});

function SplashScreenStateContextProvider({children}: ChildrenProps) {
const [splashScreenState, setSplashScreenState] = useState<ValueOf<typeof CONST.BOOT_SPLASH_STATE>>(CONST.BOOT_SPLASH_STATE.VISIBLE);
const splashScreenStateContext = useMemo(
() => ({
splashScreenState,
setSplashScreenState,
}),
[splashScreenState],
);

return <SplashScreenStateContext.Provider value={splashScreenStateContext}>{children}</SplashScreenStateContext.Provider>;
}

function useSplashScreenStateContext() {
return useContext(SplashScreenStateContext);
}

export default SplashScreenStateContext;
export {SplashScreenStateContextProvider, useSplashScreenStateContext};
5 changes: 4 additions & 1 deletion src/components/ErrorBoundary/BaseErrorBoundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import BootSplash from '@libs/BootSplash';
import GenericErrorPage from '@pages/ErrorPage/GenericErrorPage';
import UpdateRequiredView from '@pages/ErrorPage/UpdateRequiredView';
import CONST from '@src/CONST';
import {useSplashScreenStateContext} from '@src/SplashScreenStateContext';
import type {BaseErrorBoundaryProps, LogError} from './types';

/**
Expand All @@ -14,10 +15,12 @@ import type {BaseErrorBoundaryProps, LogError} from './types';

function BaseErrorBoundary({logError = () => {}, errorMessage, children}: BaseErrorBoundaryProps) {
const [errorContent, setErrorContent] = useState('');
const {setSplashScreenState} = useSplashScreenStateContext();

const catchError = (errorObject: Error, errorInfo: React.ErrorInfo) => {
logError(errorMessage, errorObject, JSON.stringify(errorInfo));
// We hide the splash screen since the error might happened during app init
BootSplash.hide();
BootSplash.hide().then(() => setSplashScreenState(CONST.BOOT_SPLASH_STATE.HIDDEN));
setErrorContent(errorObject.message);
};
const updateRequired = errorContent === CONST.ERROR.UPDATE_REQUIRED;
Expand Down
Loading
Loading