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

Pass user session information when opening the electron app from web platform #16043

Merged
merged 14 commits into from
Apr 3, 2023

Conversation

ntdiary
Copy link
Contributor

@ntdiary ntdiary commented Mar 16, 2023

Details

Fixed Issues

$ #15651
PROPOSAL: #15651 (comment)

Tests

  1. Login with account A in the desktop app.
  2. Login with account B in the web app.
  3. Navigate to the user profile page in the web app.
  4. Refresh the browser.
  5. A prompt should appear asking to 'Open the app in Electron'.
  6. Click on it and wait for the desktop app to open.
  7. Verify the user profile page for account B is displayed in the desktop app.
  • Verify that no errors appear in the JS console

Offline tests

  1. Login with account A in the desktop app.
  2. Login with account B in the web app.
  3. Navigate to the user profile page in the web app.
  4. Refresh the browser.
  5. A prompt should appear asking to 'Open the app in Electron'.
  6. Turn off the network.
  7. Click on 'Open the app in Electron' and wait for the desktop app to open.
  8. Verify the login page is displayed in the desktop app.

QA Steps

  1. Login with account A in the desktop app.
  2. Login with account B in the web app.
  3. Navigate to the user profile page in the web app.
  4. Refresh the browser.
  5. A prompt should appear asking to 'Open the app in Electron'.
  6. Click on it and wait for the desktop app to open.
  7. Verify the user profile page for account B is displayed in the desktop app.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android / native
    • Android / Chrome
    • iOS / native
    • iOS / Safari
    • MacOS / Chrome / Safari
    • MacOS / Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is correct English and approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(themeColors.componentBG)
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
chrome.mp4
Mobile Web - Chrome N/A
Mobile Web - Safari N/A
Desktop N/A
iOS N/A
Android N/A

return;
}
const exitTo = `${window.location.pathname}${window.location.search}${window.location.hash}`;
Authentication.getNewAuthToken().then((response) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should just get the session in the Onyx or use Authenticate api to get a new session like currently?
If the request fails, the electron app will not be opened.

}
this.setState({deeplinkMatch: true});
const expensifyUrl = new URL(CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL);
if (!this.props.authenticated) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Do we have some public links to open the desktop app?
And if so, do we need to clear the session information in the electron app when opening desktop app via these links?

Copy link
Contributor

Choose a reason for hiding this comment

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

@ntdiary I think if you direct user to /transition, LogOutPreviousUserPage is used. I haven't looked into this component closely, but I believe the session info for the previous user will be cleared, too.

Copy link
Contributor

@hayata-suenaga hayata-suenaga Mar 16, 2023

Choose a reason for hiding this comment

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

@ntdiary We have a custom URL scheme. new-expensify://.

I think somewhere in the code, if the app is running on a desktop, the app is registering that URL schema with Mac OS, telling the OS that if OS detects user opening any URL that starts with new-expensify://, please let the desktop app handle what to do with the URL.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yeh, now when we click 'Open the app in Electron', it will always go to the /transition page first. 😄

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 16, 2023

Hi, @thesahindia, @hayata-suenaga , there may be a few points we need to confirm, I added the comments above first. 🙂

@hayata-suenaga
Copy link
Contributor

@ntdiary I'm looking at your questions right now. I'll get back to you after I get insights into these.

@hayata-suenaga
Copy link
Contributor

hayata-suenaga commented Mar 16, 2023

@ntdiary FYI the deep link is not working on production and staging right now. local NewDot webpage -> local desktop app seems to be working correctly.

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 17, 2023

I replaced autoToken with shortLivedAuthToken.

we need to get the credentials from backend, otherwise the authToken can't be refreshed after it expires.

In my tests, the shortLivedAuthToken is valid for 1 minute. This means that if we click the button after popup has been displayed for 1 minute, the token is invalid. For this case, I temporarily let the app jump to the home page instead of getting stuck on the /transition page.

cc @thesahindia, @hayata-suenaga

@ntdiary ntdiary marked this pull request as ready for review March 17, 2023 16:56
@ntdiary ntdiary requested a review from a team as a code owner March 17, 2023 16:56
@melvin-bot melvin-bot bot requested review from hayata-suenaga and thesahindia and removed request for a team March 17, 2023 16:56
@MelvinBot
Copy link

@hayata-suenaga @thesahindia One of you needs to copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 20, 2023

@thesahindia @hayata-suenaga hi, please feel free to let me know if anything is not expected or needs to be improved. 🙂

@hayata-suenaga
Copy link
Contributor

hayata-suenaga commented Mar 20, 2023

I'm looking at your comment now I have to be offline now and I'm OOO today, but if I can find a time tonight, I'll get back to you.

@hayata-suenaga
Copy link
Contributor

@ntdiary thank you for checking on auth token lifespan.

I temporarily let the app jump to the home page instead of getting stuck on the /transition page.

Is it possible for us to intercept the event of clicking on Open Electron button and navigate the user back to the home page?

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 21, 2023

window.location.href = expensifyDeeplinkUrl;

@hayata-suenaga, I'm afraid not, this confirmation box is opened by the browser itself when we navigate to the deep link.
There doesn't seem to be an export api for javascript.

chrome source code: https://github.com/chromium/chromium/blob/676a4420834ca68ead67cb4e659db7da4fbbf63d/chrome/browser/ui/views/external_protocol_dialog.cc#L93-L94

@hayata-suenaga
Copy link
Contributor

@ntdiary what do you think is the best way to deal with the possibility that the user's auth token might expire while waiting for them to click the "Open Electron App" button?

@thesahindia
Copy link
Member

@ntdiary what do you think is the best way to deal with the possibility that the user's auth token might expire while waiting for them to click the "Open Electron App" button?

I think we can show a screen, telling the user that their auth token has expired.

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 22, 2023

How about showing this screen?

@hayata-suenaga
Copy link
Contributor

@ntdiary does the user have to sign in again? not just refreshing the screen?

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 22, 2023

@ntdiary does the user have to sign in again? not just refreshing the screen?

The shortLivedAuthToken is generated when the web page is loaded. So the user needs to refresh the page on the web platform and then click "Open the app in Electron" again. 😅

@thesahindia
Copy link
Member

How about showing this screen?

I think we should confirm this with @shawnborton.
@shawnborton, what do you think about the above screen for the below case?

User's auth token expires while waiting for them to click the "Open Electron App" button?

@hayata-suenaga
Copy link
Contributor

I'll wait @shawnborton's response but shouldn't we display "Please refresh again" instead?

And @ntdiary how do you suggest we trigger the change of the screen when the token expires?

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 22, 2023

shouldn't we display "Please refresh again" instead?

@hayata-suenaga this screen is displayed in the desktop app. If the authToken has expired, we also can't get a new valid authToken by refreshing the page in the desktop app. 😂

hayata-suenaga
hayata-suenaga previously approved these changes Mar 22, 2023
Copy link
Contributor

@hayata-suenaga hayata-suenaga left a comment

Choose a reason for hiding this comment

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

ohhh I completely misunderstood. yes it makes sense to prompt the user to log in again on the desktop side. let's wait for @shawnborton's review but otherwise LGTM!

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 29, 2023

Eh, sorry, I seem to have clicked something wrong, which caused the reviewers to change. 😅

@ntdiary
Copy link
Contributor Author

ntdiary commented Mar 30, 2023

@hayata-suenaga hi, is there anything else I can do? 🙂

@hayata-suenaga
Copy link
Contributor

@ntdiary I'm waiting for @thesahindia's review.

Copy link
Member

@thesahindia thesahindia left a comment

Choose a reason for hiding this comment

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

It looks good to me @hayata-suenaga

Copy link
Contributor

@hayata-suenaga hayata-suenaga left a comment

Choose a reason for hiding this comment

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

LGTM!

src/libs/actions/Session/index.js Outdated Show resolved Hide resolved
@ntdiary
Copy link
Contributor Author

ntdiary commented Apr 3, 2023

you mean oldPartnerUserID is always empty?

@hayata-suenaga, only in my test case. I'm not sure if it still useful in the original scenario. So maybe it's good to leave it as it is. (instead of removing it)😂

@pecanoro pecanoro merged commit e4df2d2 into Expensify:main Apr 3, 2023
@OSBotify
Copy link
Contributor

OSBotify commented Apr 3, 2023

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify
Copy link
Contributor

OSBotify commented Apr 3, 2023

🚀 Deployed to staging by https://github.com/pecanoro in version: 1.2.94-0 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 failure ❌
🕸 web 🕸 success ✅

@OSBotify
Copy link
Contributor

OSBotify commented Apr 5, 2023

🚀 Deployed to production by https://github.com/thienlnam in version: 1.2.94-3 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

Copy link
Contributor

@tgolen tgolen left a comment

Choose a reason for hiding this comment

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

Hi, I'm sorry to be coming into this late, but someone asked me about some of the code implemented in this PR and I had many questions about it.

@@ -29,27 +44,18 @@ class DeeplinkWrapper extends PureComponent {
appInstallationCheckStatus: (this.isMacOSWeb() && CONFIG.ENVIRONMENT !== CONST.ENVIRONMENT.DEV)
? CONST.DESKTOP_DEEPLINK_APP_STATE.CHECKING : CONST.DESKTOP_DEEPLINK_APP_STATE.NOT_INSTALLED,
};
this.focused = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add a code comment to explain what this is used for, or update the name of the property to this.isBrowserWindowFocused.

Copy link
Contributor Author

Choose a reason for hiding this comment

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


It comes from the original focus variable. I think we can safely remove it now, after the redirection screen is removed, I think we don't need it anymore.

this.openRouteInDesktopApp(expensifyDeeplinkUrl);
return;
}
Authentication.getShortLivedAuthToken().then((shortLivedAuthToken) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Promise chains should be EXPLICITLY avoided in code. We did a ton of work to remove all the API chaining that we should not see these reintroduced back into the code (and if Authentication would have been an action file as opposed to a lib file, ESLint would have thrown an error).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thank you for pointing this out. It has been removed.

const expensifyUrl = new URL(CONFIG.EXPENSIFY.NEW_EXPENSIFY_URL);
const expensifyDeeplinkUrl = `${CONST.DEEPLINK_BASE_URL}${expensifyUrl.host}${window.location.pathname}${window.location.search}${window.location.hash}`;
updateAppInstallationCheckStatus() {
setTimeout(() => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is there a setTimeout() here? I see this is moving some of the original code, but we should NEVER EVER have a setTimeout() in the code without a thorough code comment explaining it's necessity.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. I guess it was originally intended to check the focus with a delay. But it's not needed now since the redirection screen has been removed.

Comment on lines +95 to +97
this.setState({appInstallationCheckStatus: CONST.DESKTOP_DEEPLINK_APP_STATE.INSTALLED});
} else {
this.setState({appInstallationCheckStatus: CONST.DESKTOP_DEEPLINK_APP_STATE.NOT_INSTALLED});
Copy link
Contributor

Choose a reason for hiding this comment

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

It appears to me that these values aren't used anywhere? The only thing I see looking at this value is in the render method and it's only looking for the value of CHECKING.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, it's not needed now since the redirection screen has been removed, and we can replace appInstallationCheckStatus with isShortLivedAuthTokenLoading, the latter is clearer.

@@ -105,7 +105,17 @@ function reauthenticate(command = '') {
});
}

function getShortLivedAuthToken() {
return Network.post('OpenOldDotLink', {shouldRetry: false}).then((response) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is Network.post() used here? This is considered a deprecated method (and sorry if it was never marked as such, but we've been trying to remove this for the past year).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I also used to think that using the onyx to retrieve short live token would be a little weird, but based on your discussions, this should no longer be an issue. And if we can create the BeginDeepLinkRedirect new command, then we can also no longer call makeRequestWithSideEffects.

if (response && response.shortLivedAuthToken) {
return Promise.resolve(response.shortLivedAuthToken);
}
return Promise.reject();
Copy link
Contributor

Choose a reason for hiding this comment

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

What does this failure mean? It looks like it sets a state property in the deeplink component that either indicates that the desktop app is or isn't installed (yet, the state property is never checked for that).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yep, it's no longer needed either. We can remove it after we replace the appInstallationCheckStatus with isShortLivedAuthTokenLoading.

@@ -239,6 +240,11 @@ function signInWithShortLivedAuthToken(email, authToken) {
isLoading: true,
},
},
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: ONYXKEYS.IS_TOKEN_VALID,
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks like it's only used for showing a loading indicator in LogInWithShortLivedAuthTokenPage and has nothing to do with a token being valid or invalid. Please change the name to IS_TOKEN_LOADING. However, this is already ONYXKEYS.ACCOUNT.isLoading so why is there a need for both?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I saw ONYXKEYS.ACCOUNT.isLoading being used in the login form, not sure if it can be used in other scenarios.
And replace IS_TOKEN_VALID with ONYXKEYS.ACCOUNT.isLoading in the new followup PR.

if (response) {
return;
}
Navigation.navigate();
Copy link
Contributor

Choose a reason for hiding this comment

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

It seems like this might have something to do with one of the above scenarios, but it is not clear what. Can you please add a code comment to explain why this is here and why there is no way that this can be done optimistically before the response comes?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

For example, if a request fails due to a sudden disconnection, we need to redirect the user to the login page instead of leaving them stuck on the transition page. I tried using network.isOffline to handle this situation in the new PR and left a todo comment for review.

@hayata-suenaga
Copy link
Contributor

hayata-suenaga commented Apr 13, 2023

@tgolen thank you very much for the comments. we should have done more thorough review.

Some of the fixes suggested changed are being implemented in this PR

@ntdiary could you answer and answer @tgolen's questions above and are you also up for a followup PR for fixing issues?

@ntdiary
Copy link
Contributor Author

ntdiary commented Apr 14, 2023

@hayata-suenaga, of course, this is my responsibility.

@tgolen, thanks you very much for these comments and sorry for the confusion.
And I also re-checked the PR #16383 and this PR, then raised a new followup PR #17452, hope it can help us clean up the messy code and make the logic clearer.

@tgolen
Copy link
Contributor

tgolen commented Apr 14, 2023

Great, thank you so much for addressing these! It's gonna make this code a lot cleaner. 👍

@s77rt
Copy link
Contributor

s77rt commented Apr 18, 2023

I think this PR caused a regression #17391 (details here). This PR adds the transition prefix to all deeplinks and I think we should not do that because the transition prefix is only for OLDDOT -> NEWDOT and not WEB -> DESKTOP.

@cead22 cead22 changed the title pass user session information when opening the electron app from web platform Pass user session information when opening the electron app from web platform May 25, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants