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

NewRelic overwrites window.Promise #187

Closed
graemechapman opened this issue Feb 17, 2022 · 21 comments
Closed

NewRelic overwrites window.Promise #187

graemechapman opened this issue Feb 17, 2022 · 21 comments

Comments

@graemechapman
Copy link

Description

As per the title, replacing browser built-ins is generally a bad idea, and can interfere with other code which relies on this functionality.

Steps to Reproduce

Visit a website running NewRelic, and try to use window.Promise.

Expected Behavior

image

Relevant Logs / Console output

image

Your Environment

Browser name and version: Chrome 98
Operating System and version: macOS Big Sur

Additional context

n/a

@graemechapman graemechapman added bug Something isn't working needs triage labels Feb 17, 2022
@aubreymasten aubreymasten added medium Medium Engineering Effort and removed medium Medium Engineering Effort labels Feb 18, 2022
@aubreymasten
Copy link
Contributor

Hey @graemechapman, is there a specific issue that you're running into where our Promise instrumentation is interfering with other code?

@graemechapman
Copy link
Author

Hey @graemechapman, is there a specific issue that you're running into where our Promise instrumentation is interfering with other code?

Hi @aubreymasten I have a class extending from Promise, when run in conjunction with newrelic the additional methods on these class instances are not accessible.

I have been able to work around this for now by grabbing the original promise constructor from window.NREUM.o.PR but obviously this is not ideal.

@aubreymasten aubreymasten added this to Triage Needed: Unprioritized Features in Browser Instrumentation Engineering Board Mar 15, 2022
@aubreymasten aubreymasten moved this from Triage Needed: Unprioritized Features to Research Needed in Browser Instrumentation Engineering Board Mar 15, 2022
@kucaahbe
Copy link

have the same issue as well, when NewRelic kicks in TinyMCE editor can not start:
Знімок екрана 2022-06-29 о 2 12 51 пп

Знімок екрана 2022-06-29 о 2 14 22 пп

@graemechapman
Copy link
Author

have the same issue as well, when NewRelic kicks in TinyMCE editor can not start: Знімок екрана 2022-06-29 о 2 12 51 пп

Знімок екрана 2022-06-29 о 2 14 22 пп

This is an even bigger problem than the one I reported this for, and is likely to have a much more widespread impact.

@cwli24
Copy link
Contributor

cwli24 commented Jul 27, 2022

@kucaahbe I'm unable to reproduce this issue on a vanilla page's agent for the current (unreleased) version and a production customer's website v1216 agent. I haven't looked into TinyMCE specifically, but a potential cause for conflict as had happened before is other libraries or API implementations that also touches the global Promise object, such as Zone.js for Angular. Would like to table and revisit this after next version release if it's still an issue. Thanks!

@cwli24
Copy link
Contributor

cwli24 commented Jul 27, 2022

Hey @graemechapman, is there a specific issue that you're running into where our Promise instrumentation is interfering with other code?

Hi @aubreymasten I have a class extending from Promise, when run in conjunction with newrelic the additional methods on these class instances are not accessible.

I have been able to work around this for now by grabbing the original promise constructor from window.NREUM.o.PR but obviously this is not ideal.

Hi @graemechapman, could you clarify a little more about what and where the friction is please? I did not find any issue with calling the instance method of an inherited-from-Promise class--am I misunderstanding?

Specifically, the (spa flavor) agent will wrap around the window.Promise object and alter it; that is the functionality of the object. What problem are you attempting to solve? Do you just want access to the untampered Promise? Or is there some property or method that you cannot find on the altered Promise which should exist? An example would be appreciated, thanks.

@cwli24 cwli24 added needs follow up Issue needs follow-up to ensure the problem has been solved by a release, PR, or otherwise. and removed bug Something isn't working needs triage labels Jul 27, 2022
@kucaahbe
Copy link

@kucaahbe I'm unable to reproduce this issue on a vanilla page's agent for the current (unreleased) version and a production customer's website v1216 agent. I haven't looked into TinyMCE specifically, but a potential cause for conflict as had happened before is other libraries or API implementations that also touches the global Promise object, such as Zone.js for Angular. Would like to table and revisit this after next version release if it's still an issue. Thanks!

hello @cwli24 , thanks for the reply. In our case TinyMCE wasn't the cause of the issue: its code uses the Promise.allSettled and that's how we spotted the issue.
On the other hand, I've checked the vanilla page as well and no issues there, so I've checked our site again and found the re-producible case; the issue looks like is some conflict between the NewRelic agent code and ES6 shim library (https://github.com/paulmillr/es6-shim).
This is the test case (please note, didn't place NewRelic agent code snippet there):

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>NewRelic issue #187 test case</title>

    <script>
      if (Promise.allSettled) {
        alert('Promise.allSettled initially exists')
      } else {
        alert('Promise.allSettled does not exist')
      }
    </script>

    <script type="text/javascript">
      NewRelic agent code snippet here
    </script>

    <script src="https://cdn.jsdelivr.net/gh/paulmillr/es6-shim@0.35.6/es6-shim.js"></script>
  </head>

  <body>
    <script>
      console.log('Promise.allSettled:', Promise.allSettled)
      if (Promise.allSettled) {
        alert('Promise.allSettled exists')
      } else {
        alert('Promise.allSettled is overrided')
      }
    </script>
  </body>
</html>

@cwli24 cwli24 added bug Something isn't working and removed needs follow up Issue needs follow-up to ensure the problem has been solved by a release, PR, or otherwise. labels Aug 15, 2022
@workato-integration
Copy link

@workato-integration
Copy link

Jira CommentId: 116274
Commented by phousley:

Since our instrumentation relies on knowing when a promise is created and subsequent methods like then are invoked and whether that invocation succeeds or fails, we have to wrap the native Promise. However, in our doing so, we possibly have created a few issues where 1. window.Promise is no longer being seen as native and 2. other libraries or customer code that extends from window.Promise has issue like what we saw in the https://issues.newrelic.com/browse/NEWRELIC-4683. We are also possibly not correctly wrapping Promise since some of the other methods like allSettled is no longer available.

@workato-integration
Copy link

Jira CommentId: 116275
Commented by phousley:

We should create a couple test cases:

Compare the static methods of window.NREUM.o._PR and window.Promise and verify those static methods exist

Compare the methods of an instance of window.NREUM.o._PR and window.Promise and verify those methods exist

Create test case that uses handlers with a set context and verify that context is retained within the handler

@workato-integration
Copy link

Jira CommentId: 138424
Commented by jporter:

Promise methods like 'any' and 'allSettled' are already returned as 'Native Code' when the customer's shim is not loaded into the page, so this bug ticket is actually just an integration issue with whatever shim the reporter was using (and likely others that expect native code).  So I am just going to put in a backlog ticket to re-evaluate the way we wrap promise instead of overwriting the global and close this bug ticket.

@workato-integration
Copy link

Work has been completed on this issue.

Browser Instrumentation Engineering Board automation moved this from Research Needed to Done: Issues recently completed Jan 19, 2023
@samdenty
Copy link

This is breaking our application

CleanShot 2023-02-17 at 17 11 22@2x

@patrickhousley
Copy link
Member

@samdenty Can you look to see what version of the agent is being loaded? Maybe provide a link to your website? I just checked another site that is loading the latest version of our agent (1225) and o instanceof Promise reports true.

@samdenty
Copy link

samdenty commented Feb 17, 2023

new Promise(() => {}) instanceof Promise works
but
(async () => {})() instanceof Promise doesnt

the URL is https://stackblitz.com/~/github.com/withastro/docs (temp disabled until next week when a solution comes up)

@cwli24
Copy link
Contributor

cwli24 commented Feb 17, 2023

new Promise(() => {}) instanceof Promise works but (async () => {})() instanceof Promise doesnt

the URL is https://stackblitz.com/~/github.com/withastro/docs

Thanks for bringing this up & to our attention. v1225 included a re-working of our Promise wrapping because what we were doing before was directly modifying the native Promise that async/await implements. -- Apologies for causing breakage!

Going forward, our (wrapped) Promise will likely remain that way; we may re-discuss the implementation, though I feel this is the right thing to do. It's very similar to Bluebird's dilemma in that async/await's returned Promise cannot be replaced with our custom Promise.

I'm not sure you're going to get the (revert) solution from this agent, to be frank. However, there are ways on your end to "fix" it:

  • casting async's native Promise to our Promise using Promise.resolve(asyncFunc())
  • check it against newrelic.o.PR instead (this is the original Promise object), if instanceof is needed(?)
  • (obligatory:) using Promise instead of async/await
  • (last resort:) swap the original Promise back and use it over ours by window.Promise = newrelic.o.PR after agent code -- this will make SPA nonfunctional

@samdenty
Copy link

samdenty commented Feb 17, 2023

we're running a fork of microsoft's vscode codebase directly in the browser. We try to keep modifications to a minimum, so only the last resort option (swapping the original promise back) would work. Maybe we do that hack to keep using new relic? @fvsch

I could imagine that instanceof Promise would be used in third-party scripts that are outside of the control of end-user applications to fix, so would require grepping the source code and re-hosting the script (cause it could be on a cdn)

I'm not personally a fan of overriding globals, I would much prefer this as an option (disabled by default maybe?) - but that's just my opinion 🙂 I'm not deeply familiar with new relic though and what features overriding promise gives

@patrickhousley
Copy link
Member

@samdenty Do you have a staging environment/site that we can use to debug?

@cwli24
Copy link
Contributor

cwli24 commented Feb 17, 2023

To clarify, it's not particularly instanceof Promise, but specifically within the context of checking async's return against it that's problematic.
I imagine historically we've swapped out globals for dev convenience so they did not have to change anything vs. replacing all the Promise with something like newrelic.Promise per adoption; this could be subject to change, but I'm afraid at this point it would only offend the vast majority of existing users.

Regardless, the native global is still there (hidden) and left untouched, which to me is better than modifying its prototype, irrespective of the global ref replacement. And unfortunately, that means async is native (since we have no way to wrap the keyword) and its returned promise is native. That's the impasse, and I'm more pressed about
(async()=>{})().then(...)
not using our then than about the instanceof comparison honestly.

@samdenty
Copy link

samdenty commented Feb 17, 2023

I presumed you were already doing this and this bug was an edge case where this didn't work (hence my suggestion to add an option to disable) - but are you defining a Symbol.hasInstance on your custom promise, that returns true when both a native promise and mocked promise is passed

@patrickhousley
Copy link
Member

@samdenty I am going to open a new issue for this so we can better track it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working customer Input sync2jira
Projects
Browser Instrumentation Engineering B...
  
Done: Issues recently completed
Development

No branches or pull requests

7 participants