question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Signing in with `exitTo` doesn't work

See original GitHub issue

If you haven’t already, check out our contributing guidelines for onboarding!


Platform - version: Desktop web (all?)

Action Performed (reproducible steps):

  1. Sign in as normal
  2. Navigate to a report
  3. Click “Sign out”
  4. Note that there is an exitTo in the URL
  5. Enter an email address to sign in
  6. Note that it hangs there forever

Expected Result: Expected to see password/2FC page

Actual Result: Hung with a spinner forever

Notes/Photos/Videos: image

Logs - JS/Android/iOS (if applicable):

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:16 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
r8commented, Jan 3, 2021

I was able to reproduce it only with switching tab and coming back to the sign in tab. I’m not sure if others, who reproduced it, switched the tab in the process, but this flow has 100% reproducibility (thanks, @TheRohitSharma).

There was a post in the Slack about new steps for contributors:

  1. Hired contributors post their solution proposals directly on the GitHub issue
  2. Expensify reviews and provides feedback on proposal on GH issue
  3. Contributors move forward with creating PRs

Even though I’m itching to write the pull request, I’ll post the solution first.

Steps to reproduce

  1. Login to the web app
  2. Navigate to any chat
  3. Click “Sign Out”
  4. Switch to other tab in the browser
  5. Switch back to the Sign In page
  6. Wait for 3 seconds (I know, right?)
  7. Enter the email, press “Continue”
  8. Confirm that sign in hangs and doesn’t continue

The root cause

This is a combination of two issues.

When ReportActionsView is mounted, it adds an event listener for the app visibility change (remember the tab switching?)

    componentDidMount() {
        this.visibilityChangeEvent = AppState.addEventListener('change', () => {
            if (this.props.isActiveReport && Visibility.isVisible()) {
                this.timers.push(setTimeout(this.recordMaxAction, 3000));
            }
        });

So on each app visibility change, the function recordMaxAction will be called, and it will send the request to the server to update last read action id.

And when ReportActionsView is unmounting, it should remove this listener:

    componentWillUnmount() {
        if (this.visibilityChangeEvent) {
            this.visibilityChangeEvent.remove();
        }

The problem is AppState.addEventListener doesn’t really return anything, so this.visibilityChangeEvent is undefined after subscription to the event. It means that componentWillUnmount doesn’t remove the listener.

The listener is still active after the sign out.

visibilityChangeUndefined

When user switches to other tab and returns back to the sign in, the event triggers. After 3 seconds timeout, it sends the request. But user is signed out, so server responds with 407 code. API request function calls handleExpiredAuthToken to try to relogin user.

function handleExpiredAuthToken(originalResponse, originalCommand, originalParameters, originalType) {
    // Prevent any more requests from being processed while authentication happens
    Network.pauseRequestQueue();
    isAuthenticating = true;

    Authenticate({
        useExpensifyLogin: false,
        partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
        partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
        partnerUserID: credentials.autoGeneratedLogin,
        partnerUserSecret: credentials.autoGeneratedPassword,
    })

This is the place where other issue happens. handleExpiredAuthToken pauses the network queue and tries to send Authenticate request, using saved credentials. But user is signed out. So credentials object is null. So accessing credentials.autoGeneratedLogin and credentials.autoGeneratedPassword throws an exception, which is not caught.

So handleExpiredAuthToken ends prematurely. But before it tried to send the request, it paused the Network Queue. And after the exception the queue is still on pause.

Now user enters the email and presses “Continue” button.

fetchAccountDetails request is added to the queue, but queue is on pause, so request will never be sent and “Continue” button forever hangs in loading state.

https://user-images.githubusercontent.com/130532/103470075-10e73680-4d76-11eb-8578-94283f4d5c95.mp4

The fix

The fix should include:

  1. The null check for credentials object, or something like this:
    const authCredentials = credentials || {};

    Authenticate({
        useExpensifyLogin: false,
        partnerName: CONFIG.EXPENSIFY.PARTNER_NAME,
        partnerPassword: CONFIG.EXPENSIFY.PARTNER_PASSWORD,
        partnerUserID: authCredentials.autoGeneratedLogin,
        partnerUserSecret: authCredentials.autoGeneratedPassword,
    })
  1. Proper event listener removal in ReportActionsView:
    // Should have .bind(this) in constructor
    onVisibilityChange() {
        if (this.props.isActiveReport && Visibility.isVisible()) {
            this.timers.push(setTimeout(this.recordMaxAction, 3000));
        }
    }

    componentDidMount() {
        AppState.addEventListener('change', this.onVisibilityChange);
    }

    componentWillUnmount() {
        AppState.removeEventListener('change', this.onVisibilityChange);
    }

Prevention of future issues

I’m not sure about it yet.

  1. I could put the request in handleExpiredAuthToken to try-catch block, but we already have the catch for the promise. So this approach would require some duplication of the catch code, because promise rejection will not be caught by try-catch.

  2. I could wrap the whole function content with Promise.try(() => { ... }); and add the only one parent catch, which would handle both promise rejections and params errors. That would be cleaner, but looks a bit weird to me.

  3. The cleanest way would be async/await with try/catch, but you don’t use it anywhere in your code. So that probably makes the №2 approach the winner.

0reactions
marcaaroncommented, Jan 12, 2021

@ajimpa That solution alone will not solve this issue since it doesn’t address the underlying cause of this particular bug (which is that we have a network request firing after we have logged out).

Read more comments on GitHub >

github_iconTop Results From Across the Web

Exit function stopped working - Power Platform Community
Signout is useful when devices are shared to ensure user security. While authoring the app, calling Exit doesn't exit or sign out the...
Read more >
Why does my exit() command not work in python?
SystemExit is special in two ways: It doesn't dump a traceback and exit with a fixed status 1 if unhandled (it just silently...
Read more >
What's wrong with exit command on my terminal?
When you exited, your session was closed - but you opened this session as a user, so you get back to your user...
Read more >
Troubleshoot the AWS Glue error "Command failed with exit ...
My AWS Glue extract, transform, and load (ETL) job fails with the error "Command failed with exit code". Short description. You get this...
Read more >
I received an error message saying, “You've reached the ...
To fix this issue, you will need to close your Internet browser and open it back up before trying to log in again....
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found