Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
db996cb
clean up the deep link code
ntdiary Apr 14, 2023
cc48aaf
Add some comments to explain the deep link code
ntdiary Apr 14, 2023
728cc18
Update some comments to explain the deep link code
ntdiary Apr 17, 2023
f1f19a7
Update comment for removing the short-lived auth token
ntdiary Apr 17, 2023
4945eb1
specify the route to redirect
ntdiary Apr 17, 2023
6dc06df
Update comment for removing the short-lived auth token
ntdiary Apr 17, 2023
cff9643
makeRequestWithSideEffects can be removed in the PR 17364
ntdiary Apr 19, 2023
3c73416
rename TRANSITION_FROM_OLD_DOT to TRANSITION
ntdiary Apr 25, 2023
a129009
If the current link is transition from oldDot, we should wait for it …
ntdiary Apr 25, 2023
cb5f434
prevent the popup from appearing repeatedly
ntdiary Apr 26, 2023
1f252d6
rename TRANSITION to TRANSITION_BETWEEN_APPS
ntdiary Apr 26, 2023
5f71937
rename variable and explain the reason for the code.
ntdiary Apr 26, 2023
a737600
correct the condition of variable hasPopupBeenOpenedBefore
ntdiary Apr 26, 2023
c7b323d
Refactor the deeplink logic:
ntdiary Apr 28, 2023
c5ee140
try to improve the transition logic.
ntdiary May 2, 2023
d9a3cbc
add a comment for openRouteInDesktopApp
ntdiary May 2, 2023
5e2a6d9
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 3, 2023
54ee01c
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 3, 2023
0349229
move navigation logic from createWorkspace to createWorkspaceAndNavig…
ntdiary May 4, 2023
d858603
fix lint errors
ntdiary May 4, 2023
65b0f7c
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 5, 2023
4105086
Update src/libs/actions/App.js
ntdiary May 5, 2023
edbae30
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 9, 2023
637af52
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 10, 2023
bb0ddd0
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 11, 2023
8913f88
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 15, 2023
03d69bb
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 16, 2023
bdaf961
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 17, 2023
8758e31
Revert the revert regarding createWorkspaceAndNavigateToIt.
ntdiary May 17, 2023
144abda
fix lint error
ntdiary May 17, 2023
9e7f595
Update src/libs/actions/App.js
ntdiary May 17, 2023
fbdbb7d
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 18, 2023
96be874
casing mistake
ntdiary May 18, 2023
ac8ad4e
Do not call clearAccountMessages on non-home screens.
ntdiary May 19, 2023
f9f89da
add the comment for isFocused
ntdiary May 22, 2023
e465908
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 23, 2023
92faead
add a skipReauthentication param
ntdiary May 23, 2023
5a66cbc
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 23, 2023
b0adfd3
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 24, 2023
3faec2b
Merge branch 'main' into clean-up-deep-link-code
ntdiary May 31, 2023
4ed6c97
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jun 6, 2023
e195537
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jun 7, 2023
e31d0bd
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jun 9, 2023
7d70e7b
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jun 12, 2023
d5937cc
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jun 14, 2023
4a69d1e
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jun 15, 2023
be41317
we still need the currentUserEmail variable.
ntdiary Jun 15, 2023
57ef6bd
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jun 22, 2023
db7a9cb
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jun 28, 2023
0c87306
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 4, 2023
8c7c2c4
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 5, 2023
b6b672f
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 6, 2023
1298b0f
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 9, 2023
2bb7f31
keep variable name consistent.
ntdiary Jul 9, 2023
61acb1b
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 11, 2023
80101fa
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 12, 2023
6d3bdce
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 17, 2023
bf37347
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 18, 2023
3120a38
Merge branch 'main' into clean-up-deep-link-code
ntdiary Jul 18, 2023
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
3 changes: 0 additions & 3 deletions src/ONYXKEYS.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,6 @@ export default {
// Is app in beta version
IS_BETA: 'isBeta',

// Whether the auth token is valid
IS_TOKEN_VALID: 'isTokenValid',

// The theme setting set by the user in preferences.
// This can be either "light", "dark" or "system"
PREFERRED_THEME: 'preferredTheme',
Expand Down
2 changes: 1 addition & 1 deletion src/ROUTES.js
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ export default {
getReportWelcomeMessageRoute: (reportID) => `r/${reportID}/welcomeMessage`,
REPORT_SETTINGS_WRITE_CAPABILITY: 'r/:reportID/settings/who-can-post',
getReportSettingsWriteCapabilityRoute: (reportID) => `r/${reportID}/settings/who-can-post`,
TRANSITION_FROM_OLD_DOT: 'transition',
TRANSITION_BETWEEN_APPS: 'transition',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NAB, is it really "between" apps though? We are actually coming from OldDot I think and don't use this screen to navigate to OldDot. This change makes it more ambiguous IMO and doesn't seem necessary.

VALIDATE_LOGIN: 'v/:accountID/:validateCode',
GET_ASSISTANCE: 'get-assistance/:taskID',
getGetAssistanceRoute: (taskID) => `get-assistance/${taskID}`,
Expand Down
2 changes: 1 addition & 1 deletion src/SCREENS.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default {
REPORT: 'Report',
REPORT_ATTACHMENTS: 'ReportAttachments',
NOT_FOUND: 'not-found',
TRANSITION_FROM_OLD_DOT: 'TransitionFromOldDot',
TRANSITION_BETWEEN_APPS: 'TransitionBetweenApps',
SETTINGS: {
PREFERENCES: 'Settings_Preferences',
},
Expand Down
141 changes: 13 additions & 128 deletions src/components/DeeplinkWrapper/index.website.js
Original file line number Diff line number Diff line change
@@ -1,157 +1,42 @@
import PropTypes from 'prop-types';
import React, {PureComponent} from 'react';
import {withOnyx} from 'react-native-onyx';
import FullScreenLoadingIndicator from '../FullscreenLoadingIndicator';
import styles from '../../styles/styles';
import {PureComponent} from 'react';
import Str from 'expensify-common/lib/str';
import * as Browser from '../../libs/Browser';
import ROUTES from '../../ROUTES';
import * as App from '../../libs/actions/App';
import CONST from '../../CONST';
import CONFIG from '../../CONFIG';
import * as Browser from '../../libs/Browser';
import ONYXKEYS from '../../ONYXKEYS';
import * as Authentication from '../../libs/Authentication';
import DeeplinkRedirectLoadingIndicator from './DeeplinkRedirectLoadingIndicator';
import * as Session from '../../libs/actions/Session';

const propTypes = {
/** Children to render. */
children: PropTypes.node.isRequired,

/** Session info for the currently logged-in user. */
session: PropTypes.shape({
/** Currently logged-in user email */
email: PropTypes.string,

/** Currently logged-in user authToken */
authToken: PropTypes.string,
}),
};

const defaultProps = {
session: {
email: '',
authToken: '',
},
};

class DeeplinkWrapper extends PureComponent {
constructor(props) {
super(props);

this.state = {
appInstallationCheckStatus:
this.isMacOSWeb() && CONFIG.ENVIRONMENT !== CONST.ENVIRONMENT.DEV ? CONST.DESKTOP_DEEPLINK_APP_STATE.CHECKING : CONST.DESKTOP_DEEPLINK_APP_STATE.NOT_INSTALLED,
shouldOpenLinkInBrowser: false,
};
this.focused = true;
this.openLinkInBrowser = this.openLinkInBrowser.bind(this);
}

componentDidMount() {
if (!this.isMacOSWeb() || CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV) {
return;
}

window.addEventListener('blur', () => {
this.focused = false;
});

const expensifyUrl = new URL(CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL);
const params = new URLSearchParams();
params.set('exitTo', `${window.location.pathname}${window.location.search}${window.location.hash}`);
if (!this.props.session.authToken) {
const expensifyDeeplinkUrl = `${CONST.DEEPLINK_BASE_URL}${expensifyUrl.host}/transition?${params.toString()}`;
this.openRouteInDesktopApp(expensifyDeeplinkUrl);
return;
}

// There's no support for anonymous users on desktop
if (Session.isAnonymousUser()) {
// If the current url path is /transition..., meaning it was opened from oldDot, during this transition period:
// 1. The user session may not exist, because sign-in has not been completed yet.
// 2. There may be non-idempotent operations (e.g. create a new workspace), which obviously should not be executed again in the desktop app.
// So we need to wait until after sign-in and navigation are complete before starting the deeplink redirect.
if (Str.startsWith(window.location.pathname, Str.normalizeUrl(ROUTES.TRANSITION_BETWEEN_APPS))) {
App.beginDeepLinkRedirectAfterTransition();
return;
}

Authentication.getShortLivedAuthToken()
.then((shortLivedAuthToken) => {
params.set('email', this.props.session.email);
params.set('shortLivedAuthToken', `${shortLivedAuthToken}`);
const expensifyDeeplinkUrl = `${CONST.DEEPLINK_BASE_URL}${expensifyUrl.host}/transition?${params.toString()}`;
this.openRouteInDesktopApp(expensifyDeeplinkUrl);
})
.catch(() => {
// If the request is successful, we call the updateAppInstallationCheckStatus before the prompt pops up.
// If not, we only need to make sure that the state will be updated.
this.updateAppInstallationCheckStatus();
});
}

updateAppInstallationCheckStatus() {
setTimeout(() => {
if (!this.focused) {
this.setState({appInstallationCheckStatus: CONST.DESKTOP_DEEPLINK_APP_STATE.INSTALLED});
} else {
this.setState({appInstallationCheckStatus: CONST.DESKTOP_DEEPLINK_APP_STATE.NOT_INSTALLED});
}
}, 500);
}

openRouteInDesktopApp(expensifyDeeplinkUrl) {
this.updateAppInstallationCheckStatus();

const browser = Browser.getBrowser();

// This check is necessary for Safari, otherwise, if the user
// does NOT have the Expensify desktop app installed, it's gonna
// show an error in the page saying that the address is invalid
// It is also necessary for Firefox, otherwise the window.location.href redirect
// will abort the fetch request from NetInfo, which will cause the app to go offline temporarily.
if (browser === CONST.BROWSER.SAFARI || browser === CONST.BROWSER.FIREFOX) {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.contentWindow.location.href = expensifyDeeplinkUrl;

// Since we're creating an iframe for Safari to handle
// deeplink we need to give this iframe some time for
// it to do what it needs to do. After that we can just
// remove the iframe.
setTimeout(() => {
if (!iframe.parentNode) {
return;
}

iframe.parentNode.removeChild(iframe);
}, 100);
} else {
window.location.href = expensifyDeeplinkUrl;
}
App.beginDeepLinkRedirect();
}

isMacOSWeb() {
return !Browser.isMobile() && typeof navigator === 'object' && typeof navigator.userAgent === 'string' && /Mac/i.test(navigator.userAgent) && !/Electron/i.test(navigator.userAgent);
}

openLinkInBrowser() {
this.setState({shouldOpenLinkInBrowser: true});
}

shouldShowDeeplinkLoadingIndicator() {
const routeRegex = new RegExp(CONST.REGEX.ROUTES.VALIDATE_LOGIN);
return routeRegex.test(window.location.pathname);
}

render() {
if (this.state.appInstallationCheckStatus === CONST.DESKTOP_DEEPLINK_APP_STATE.CHECKING) {
return <FullScreenLoadingIndicator style={styles.flex1} />;
}

if (this.state.appInstallationCheckStatus === CONST.DESKTOP_DEEPLINK_APP_STATE.INSTALLED && this.shouldShowDeeplinkLoadingIndicator() && !this.state.shouldOpenLinkInBrowser) {
return <DeeplinkRedirectLoadingIndicator openLinkInBrowser={this.openLinkInBrowser} />;
}

return this.props.children;
}
}

DeeplinkWrapper.propTypes = propTypes;
DeeplinkWrapper.defaultProps = defaultProps;
export default withOnyx({
session: {key: ONYXKEYS.SESSION},
})(DeeplinkWrapper);
export default DeeplinkWrapper;
11 changes: 1 addition & 10 deletions src/libs/Authentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,13 +99,4 @@ function reauthenticate(command = '') {
});
}

function getShortLivedAuthToken() {
return Network.post('OpenOldDotLink', {shouldRetry: false}).then((response) => {
if (response && response.shortLivedAuthToken) {
return Promise.resolve(response.shortLivedAuthToken);
}
return Promise.reject();
});
}

export {reauthenticate, Authenticate, getShortLivedAuthToken};
export {reauthenticate, Authenticate};
4 changes: 3 additions & 1 deletion src/libs/Browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ function isMobileChrome() {
return false;
}

export {getBrowser, isMobile, isMobileSafari, isMobileChrome};
function openRouteInDesktopApp() {}

export {getBrowser, isMobile, isMobileSafari, isMobileChrome, openRouteInDesktopApp};
42 changes: 41 additions & 1 deletion src/libs/Browser/index.web.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import CONST from '../../CONST';
import CONFIG from '../../CONFIG';

/**
* Fetch browser name from UA string
Expand Down Expand Up @@ -60,4 +61,43 @@ function isMobileChrome() {
return /Android/i.test(userAgent) && /chrome|chromium|crios/i.test(userAgent);
}

export {getBrowser, isMobile, isMobileSafari, isMobileChrome};
/**
* The session information needs to be passed to the Desktop app, and the only way to do that is by using query params. There is no other way to transfer the data.
* @param {String} shortLivedAuthToken
* @param {String} email
*/
function openRouteInDesktopApp(shortLivedAuthToken = '', email = '') {
const params = new URLSearchParams();
Comment thread
tgolen marked this conversation as resolved.
params.set('exitTo', `${window.location.pathname}${window.location.search}${window.location.hash}`);
if (email && shortLivedAuthToken) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this means that either both of them are passed or none of them are?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, These two are the required parameters for signing in. If these two parameters are not present, such as when the login page is opened in a browser, the use can also open the login page in the desktop app via deep-link. This is consistent with our production environment. : )

params.set('email', email);
params.set('shortLivedAuthToken', shortLivedAuthToken);
}
const expensifyUrl = new URL(CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL);
const expensifyDeeplinkUrl = `${CONST.DEEPLINK_BASE_URL}${expensifyUrl.host}/transition?${params.toString()}`;

const browser = getBrowser();

// This check is necessary for Safari, otherwise, if the user
// does NOT have the Expensify desktop app installed, it's gonna
// show an error in the page saying that the address is invalid.
// It is also necessary for Firefox, otherwise the window.location.href redirect
// will abort the fetch request from NetInfo, which will cause the app to go offline temporarily.
if (browser === CONST.BROWSER.SAFARI || browser === CONST.BROWSER.FIREFOX) {
const iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
iframe.contentWindow.location.href = expensifyDeeplinkUrl;

// Since we're creating an iframe for Safari to handle deeplink,
// we need to give Safari some time to open the pop-up window.
// After that we can just remove the iframe.
setTimeout(() => {
document.body.removeChild(iframe);
}, 0);
} else {
window.location.href = expensifyDeeplinkUrl;
}
}

export {getBrowser, isMobile, isMobileSafari, isMobileChrome, openRouteInDesktopApp};
7 changes: 6 additions & 1 deletion src/libs/Middleware/Reauthentication.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,12 @@ function Reauthentication(response, request, isFromSequentialQueue) {
// of the new response created by handleExpiredAuthToken.
const shouldRetry = lodashGet(request, 'data.shouldRetry');
const apiRequestType = lodashGet(request, 'data.apiRequestType');
if (!shouldRetry && !apiRequestType) {

// For the SignInWithShortLivedAuthToken command, if the short token expires, the server returns a 407 error,
// and credentials are still empty at this time, which causes reauthenticate to throw an error (requireParameters),
// and the subsequent SaveResponseInOnyx also cannot be executed, so we need this parameter to skip the reauthentication logic.
const skipReauthentication = lodashGet(request, 'data.skipReauthentication');
if ((!shouldRetry && !apiRequestType) || skipReauthentication) {
if (isFromSequentialQueue) {
return data;
}
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/AppNavigator/AuthScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ class AuthScreens extends React.Component {
}}
/>
<RootStack.Screen
name={SCREENS.TRANSITION_FROM_OLD_DOT}
name={SCREENS.TRANSITION_BETWEEN_APPS}
options={defaultScreenOptions}
getComponent={() => {
const LogOutPreviousUserPage = require('../../../pages/LogOutPreviousUserPage').default;
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/AppNavigator/PublicScreens.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function PublicScreens() {
component={SignInPage}
/>
<RootStack.Screen
name={SCREENS.TRANSITION_FROM_OLD_DOT}
name={SCREENS.TRANSITION_BETWEEN_APPS}
options={defaultScreenOptions}
component={LogInWithShortLivedAuthTokenPage}
/>
Expand Down
2 changes: 1 addition & 1 deletion src/libs/Navigation/linkingConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export default {
// Main Routes
ValidateLogin: ROUTES.VALIDATE_LOGIN,
UnlinkLogin: ROUTES.UNLINK_LOGIN,
[SCREENS.TRANSITION_FROM_OLD_DOT]: ROUTES.TRANSITION_FROM_OLD_DOT,
[SCREENS.TRANSITION_BETWEEN_APPS]: ROUTES.TRANSITION_BETWEEN_APPS,
Concierge: ROUTES.CONCIERGE,
[SCREENS.REPORT_ATTACHMENTS]: ROUTES.REPORT_ATTACHMENTS,

Expand Down
Loading