Don't forget to update your Babel and core-js versions

Another day at work, another interesting bug to investigate. I'm writing this post because I love to debug issues. And also because, in my career, I got asked multiple times during the interviews to describe this kind of scenarios. Well, this is one of them.

The problem

There are some clients who report getting a generic error message when accessing one of the main flows of the app. The flow consists of a simple listing of products. An API is called, the response is processed and then rendered. Nothing complex, and the response is the same for everybody.

I got the API logs for one of the client, and the requests got a 200 status with a plausible Content-Length header. That's not good because it means it's not a backend issue.

In the same logs I checked the User-Agent, Chrome 109 on Windows 10. Nothing shady here!

Reverse engineering

I checked the code to see for which scenarios is the generic error message displayed. We have a simple catch, so for whatever goes wrong, the message gets displayed. The code looks like this:

connectedCallback() {
    super.connectedCallback();

    this.FooService.getData().then(response => {
        // process the response
    }).catch(() => {
        this.showGenericError = true;
    });
}

We have a logger to log whatever you want, whenever you want, and some event handlers for error and unhandledrejection events. For this case the event handlers are useless since the error is swallowed.

A lot of things can fail to end up in this catch. Dead end here.

Finding a pattern

I got the API logs for other clients. All had 200 status and a plausible Content-Length header.

I say plausible Content-Length header because we have a misconfiguration in the backend where you can get a 200 status for an invalid session.

Then I checked the User-Agent and finally some interesting things. All of them use Chrome 109.

Still weird. Why an error with such a recent version of Chrome? (like one year old)

Checking the analytics data

Next, I called my "analytics" colleagues. I asked them to check how many users with Chrome 109 tried to access that flow vs. how many actually visited it.

Here we have some luck. The flow has only one entry point (a button) through which it can be accessed. And we have tracking on that button. Also, we have tracking for the page itself - a page view event.

But, as I observed in the code, we don't send the page view event in case of this generic error.

connectedCallback() {
    super.connectedCallback();

    this.FooService.getData().then(response => {
        // process the response

        // send page view event
        this.AnalyticsService.sendPageView();
    }).catch(() => {
        this.showGenericError = true;
    });
}

So, our analytics platform should tell us the difference between clicks and successfully viewed. They confirmed the difference for Chrome 109. Nice!

I asked them to remove the filter for Chrome, and many more cases showed up. Interesting. So it's not just Chrome 109 and the impact is bigger than expected.

Reproduction

It's time to reproduce it on my machine.

I searched a little bit, but finally I managed to get Chromium 109 on my machine.

I opened the local app in Chromium and BOOM, there it is. Same error! 🎉

Once you can reproduce a bug, it's already fixed.

Using the local app, I just put a breaking point in the "catch" which shows the generic error message.

And I got the error. A beautiful TypeError about toSorted() not being a function.

".toSorted is not a function"

I checked the compatibility table, and there we go: Chrome 110.

toSorted() method compatibility table

Solution

I updated the Babel and core-js versions, so the toSorted() method gets polyfilled.

That's it! Another productive day at work. 💅