[BUG] Non-serializable values polluting React Router navigation state
Description/Screenshot First of all, I'm sorry for such a long post, but I'm at my wits end as I've been investigating this issue for days and I'm no closer to fixing it (except for workarounds illustrated below).
I'm encountering a sporadic error in one of my React projects when ApplicationInsights-JS is enabled. The error occurs only when navigating using React Router (v 5.3) and passing an array in the history.state, and it happens regardless of:
- Browser used (tested in latest Chrome, Firefox)
- ApplicationInsights-JS version (tested multiple 3.x versions)
- Build type (development and production)
- SDK config (I've ran the same config in a smaller RR5 project without issues)
The error message varies based on the place that triggered it (3rd party library, my code..), but here are some occurences I was able to document:
-
DataCloneError: Failed to execute 'pushState' on 'History': function (…) could not be cloned -
Uncaught DOMException: Function object could not be cloned
This issue does not happen at all when ApplicationInsights is disabled. I was unable to test this with newer versions of React Router since they contain a lot of breaking changes.
Steps to Reproduce I would love to provide reliable steps to reproduce, however, I was not able to simulate this in my other projects, so it must be some combination of the packages I'm using:
- package.json
- OS/Browser: Firefox 145, Chrome 143
- SDK Version [e.g. 22]: 3.1.x/3.3.x + 17.1.2 (react-js)
- How you initialized the SDK:
this.appInsights = new ApplicationInsights({
config: {
connectionString: `InstrumentationKey=${AIInstrumentationKey};IngestionEndpoint=/`,
enableUnhandledPromiseRejectionTracking: true,
eventsLimitInMem: 2500,
disableInstrumentationKeyValidation: true,
userOverrideEndpointUrl: AIEndpointUrl,
isStorageUseDisabled: true,
enableSessionStorageBuffer: false,
maxBatchInterval: 10000,
maxBatchSizeInBytes: 250000,
extensions: [this.reactPlugin],
extensionConfig: {
[this.reactPlugin.identifier]: {
history: browserHistory,
},
},
},
})
this.appInsights.loadAppInsights()
The pieces of code which tend to raise these exceptions:
history.push({
pathname: location.pathname,
search: searchParams.toString(),
// Workaround for the serialization issue:
// state: JSON.parse(JSON.stringify(location.state)),
state: location.state, // original code
})
const state: MultiStepFlowData = {
// Workaround for the serialization issue:
// ...JSON.parse(JSON.stringify(historyState ?? {})),
...(historyState ?? {}), // original code
selectedIds: [...newValue],
}
history.replace({ state })
Expected behavior ApplicationInsights-JS does not interfere with any other libraries in a project.
Additional context ℹ️ After days of debugging, I was able to confirm that ApplicationInsights-JS hooks into some of the browser history APIs by monkey-patching them which, in my opinion, could be the cause of this behavior. You can also see the very same "Patching" string in one of my exception screenshots above: https://github.com/microsoft/ApplicationInsights-JS/blob/4835556ce61ba902e4da0b85e76d7f175a39ffea/shared/AppInsightsCore/src/Config/DynamicProperty.ts#L41
One other place which suggests the ApplicationInsights-JS is doing something weird is this one, since the "ai_dynCfg_1" string is visible on one of the screenshots below: https://github.com/microsoft/ApplicationInsights-JS/blob/7d7e906380544e511fcb595384180434aaa1db2a/shared/AppInsightsCore/src/Config/DynamicSupport.ts#L12
When I inspected my history state (which I get via useLocation) in the Chrome debugger, I was able to confirm a number of deviations from a "pure" JS Array value which I get when the SDK is disabled:
- The value itself was accessible only via getters/setters, it wasn't available on initial hover. This probably causes no issues during usual runtime, however it might be an issue when dealing with history state
- The individual elements in the array contained additional Symbols and more getters/setters - see screenshots below.
The "good" (unpolluted) history state, vs. the "polluted" state:
Hmm, the dynamic code you referenced is supposed to be ONLY for the config... It should not be applied to the history as of this PR which explicitly exempted the history object from being converted via this setting. (which was included in 17.0.0 (of react extension) -- although your package.json says your using version 17.1.2
What type of "object" is the browserHistory object as there is logic to detect things like classes / readonly objects to block changes... We also introduced a helper function blockDynamicConversion, you can probably try the workaround specified in this issue, specifically this change (which is now supposed to happen automatically to avoid this
@MSNev interesting, the blockDynamicConversion worked and now I get no errors:
this.appInsights = new ApplicationInsights({
config: {
...
extensions: [this.reactPlugin as unknown as ITelemetryPlugin],
extensionConfig: {
[this.reactPlugin.identifier]: {
// The "history" object is just the result of "createBrowserHistory()" call
history: blockDynamicConversion(history),
},
},
},
})
Does this indicate an issue with my setup or is it some weird, edge-case behavior of the SDK itself? I'm planning on moving away from react-router and its usage of history state altogether, however, I am a little afraid that I'll stumble upon this issue again in some other scenario.
Hmm, that is odd... It may indicate that either the defaults are not getting applied correctly (which they are supposed to) or that you may be using an older version (probably unlikely).
Lets leave this issue open so that if the team gets a chance to investigate, we can use this issue as the work item... Although, realistically it's likely to get auto closed before we get to it 🥲
Notes for future investigation
It appears that the default block config is not getting applied when objects are passed. Need to confirm if this is the case and fix.