-
Notifications
You must be signed in to change notification settings - Fork 49
refactor: make improvements to NetworkObserver #1074
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
refactor: make improvements to NetworkObserver #1074
Conversation
✅ No documentation updates required. |
✅ No documentation updates required. |
… AMP-125616/move-size-calcs-out-of-observer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors network event handling so that the networkObserver no longer performs computations for request/response body sizes and the fetch override safely handles optional parameters. Key changes include:
- Eliminating in-observer computations by deferring header and body size calculations to downstream consumers.
- Updating the fetch override to safely access parameters using optional chaining.
- Adjusting tests and utilities to support the new event payload structure.
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
File | Description |
---|---|
packages/session-replay-browser/test/session-replay.test.ts | Updated tests to verify header conversion and safe fetch override behavior. |
packages/session-replay-browser/src/session-replay.ts | Added headerToObject helper and removed direct computations for body sizes. |
packages/plugin-network-capture-browser/test/autocapture-plugin/track-network-event.test.ts | Modified tests for new event processing and safe parameter handling. |
packages/plugin-network-capture-browser/src/track-network-event.ts | Updated logic to calculate body sizes using dedicated utilities. |
packages/analytics-core/test/network-observer.test.ts | Refactored tests to validate changes in fetch override and event payload. |
packages/analytics-core/src/network-observer.ts | Adjusted fetch override to safely handle optional input and removed in-observer computations. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the network observer implementation to remove internal computations for request and response body sizes and instead delegate these computations downstream. Key changes include:
- Removing computations in networkObserver and updating the event callback in SessionReplay.
- Adjusting the fetch override to safely handle optional parameters and Headers conversion.
- Updating tests across the session-replay, network-capture, and analytics-core packages to reflect these changes.
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
packages/session-replay-browser/test/session-replay.test.ts | Updated tests to verify Headers conversion and removal of body size computations. |
packages/session-replay-browser/src/session-replay.ts | Added headerToObject helper and modified event callback to remove requestBody and convert headers. |
packages/plugin-network-capture-browser/test/autocapture-plugin/track-network-event.test.ts | Revised tests to expect updated header objects and adjusted requestBodySize expectations. |
packages/plugin-network-capture-browser/src/track-network-event.ts | Introduced getRequestBodyLength and calculateResponseBodySize improvements with safe handling. |
packages/analytics-core/test/network-observer.test.ts | Modified tests for the new fetch override behavior and header handling. |
packages/analytics-core/src/network-observer.ts | Refactored the fetch override to handle optional input and remove internal header conversion calculations. |
packages/plugin-network-capture-browser/src/track-network-event.ts
Outdated
Show resolved
Hide resolved
… AMP-125616/move-size-calcs-out-of-observer
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors the network observer and fetch override to remove unnecessary computations and shift body size calculations into dedicated wrapper classes while updating test cases accordingly.
- Use RequestWrapper and ResponseWrapper to compute header and body size information.
- Update tests to reflect the changes in event serialization and fetch override behavior.
- Refactor the fetch override to avoid restoring the original fetch when unsubscribing.
Reviewed Changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
File | Description |
---|---|
packages/plugin-network-capture-browser/test/autocapture-plugin/track-network-event.test.ts | Adjusted tests to use new wrapper objects and updated expectations for network event properties. |
packages/plugin-network-capture-browser/src/track-network-event.ts | Updated event creation to use requestWrapper/responseWrapper and switched to using parseUrl with optional input. |
packages/analytics-core/src/network-observer.ts | Refactored the fetch override, introduced RequestWrapper/ResponseWrapper classes, and removed fetch restoration logic. |
Comments suppressed due to low confidence (2)
packages/plugin-network-capture-browser/test/autocapture-plugin/track-network-event.test.ts:39
- [nitpick] The use of 'as any' in test cases hides potential type mismatches. Replacing these casts with explicit types could improve test clarity and long-term maintainability.
public requestWrapper = { bodySize: 100, headers: { 'Content-Type': 'application/json' } } as any,
packages/plugin-network-capture-browser/src/track-network-event.ts:160
- Since the type casting to NetworkRequestEvent has been removed in favor of using wrapper objects, ensure that downstream consumers handle cases where requestWrapper or responseWrapper might be undefined, to avoid unexpected runtime errors.
const request = networkEvent.event;
// terminate if we reach the maximum number of entries | ||
// to avoid performance issues in case of very large FormData | ||
if (++count >= this.MAXIMUM_ENTRIES) { | ||
this._bodySize = undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would essentially return undefined to the event property?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's correct yes. Essentially it's "giving up" once it's reached the limit and not computing the size.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The performance concerns here are that the size calculation can bottleneck the request?
The size calculations and also the construction of the "requestHeaders".
|
get headers(): Record<string, string> { | ||
if (this._headers) return this._headers; | ||
const headers: Record<string, string> = {}; | ||
this.response.headers.forEach((value, key) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this still works, but do we want any additional header checks as well to be safe?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to specification, a Headers object is the only type that is set in the Response.headers
object so we shouldn't expect there to ever be a Record or Array type (like what request has).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about the off chance that is undefined
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah good thinking. I'll add a defensive check to it.
// if the response is not undefined, return it | ||
return originalResponse; | ||
} else { | ||
throw originalError; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the original error thrown by the wrapped response?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is correct yes. This is where the original error gets caught
// 2. make the call to the original fetch and preserve the response or error
let originalResponse, originalError;
try {
originalResponse = await originalFetch(requestInfo as RequestInfo | URL, requestInit);
} catch (err) {
// Capture error information
originalError = err;
}
It's essential that originalResponse
is the only object that is ever returned and that originalError
is the only erro that is ever thrown.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR refactors network observation and event tracking to address performance overhead and type safety issues in the fetch override.
- Removed redundant body size calculations by introducing request and response wrappers.
- Refactored the fetch override to safely handle optional inputs and to prevent unsafe restoration of the original fetch.
- Updated tests and exports to reflect the new modular structure for network events.
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
packages/plugin-network-capture-browser/test/autocapture-plugin/track-network-event.test.ts | Updated tests to verify the new wrapper structures and serialization logic. |
packages/plugin-network-capture-browser/src/track-network-event.ts | Adjusted event tracking to use wrapper properties for body sizes. |
packages/analytics-core/src/network-request-event.ts | Introduced RequestWrapper and ResponseWrapper classes for improved encapsulation. |
packages/analytics-core/src/network-observer.ts | Restructured the fetch override and event handling to improve performance and type safety. |
packages/analytics-core/src/index.ts | Updated exports to align with the refactored network event modules. |
Comments suppressed due to low confidence (1)
packages/plugin-network-capture-browser/test/autocapture-plugin/track-network-event.test.ts:38
- [nitpick] Avoid using 'as any' in test assertions if possible, or add a comment explaining its necessity, to maintain type clarity in tests.
}) as any,
if (!this.globalScope || !this.originalFetch) { | ||
if (startTime === undefined || durationStart === undefined) { | ||
// if we reach this point, it means that the performance API is not supported | ||
// so we can't construct a NetworkRequestEvent |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If performance timestamps are unavailable and the event is silently skipped, consider logging a debug message to indicate why the network event callback was not triggered.
// so we can't construct a NetworkRequestEvent | |
// so we can't construct a NetworkRequestEvent | |
this.logger?.debug('Network event callback skipped: performance timestamps are unavailable. This may indicate that the performance API is not supported in the current environment.'); |
Copilot uses AI. Check for mistakes.
requestBodySize: getRequestBodyLength(init?.body as FetchRequestBody), | ||
|
||
// parse the URL and Method | ||
let url: string | undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The use of optional chaining on built-in functions (e.g., Date.now?() and performance.now?()) in getTimestamps() is unconventional; since these functions are standard in browsers, removing optional chaining would make the code clearer.
Copilot uses AI. Check for mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall LGTM! Thanks for doing this! As long as we are minimizing the effect on the customer's network requests, then we should be good.
Thanks! Yeah I'm mostly concerned about preserving fetch and making it future-proof so that nobody can break it (easily). |
6359e8c
into
AMP-125616/fetch-hardening-xhr-support
PROBLEMS
"networkObserver" does computations to calculate request body size and response body size. Although the computations are fairly basic, this could introduce a unnecessary overhead when an application does a very large amount of fetches Performance issue
our current "fetch" override implementation is too strongly typed and doesn't represent how it could be used in a browser (e.g.: we call input.toString() but input may be null or undefined when it's called in the wild) bug
our fetch override doesn't parse URL and HTTP Method when the first param is of type Request, which has a ".url" and ".method" attribute. bug
restoring "fetch" to it's original fetch after unsubscribing is dangerous because if another library also overrided fetch after us, then it's implementation will get wiped out too. bug
the request "Headers" type was being cast as a Record<string, string> but it can also be a Headers instance or an array of arrays (e.g.:
[[<header>, <value>],...]
) bugthere's some redundancy in the "try" and "catch" blocks where 'originalFetch' is called (same code being called)
SOLUTIONS
refactor "networkObserver" so that it doesn't calculate requestBodySize or responseBodySize, but rather the downstream subscribers can calculate those numbers, if they need them.
refactor the "fetch" override so that it accepts the 2 parameters as "optional" and then safely accesses everything (e.g.: replace
input.toString()
withinput?.toString?.()
if the first parameter is of type Request, get URL from "input.url" and get method from "input.method". If not, fallback to url = "input?.toString?.()" and method = init.method (init is 2nd parameter) If "input.method" and "init.method" are both set, default to "init.method" (as per the spec)
don't restore fetch even when all callbacks are unsubscribed. Leave it in an overriden state so that we don't mess with others.
handle Arrays and Headers in the
get headers()
method of the request wrappermove the NetworkRequestEvent creation after the original fetch is called, and then return/throw the original response/error that the original fetch returned/throws
COMMENTS
e.g.) the fetch override looks like this (simplified)
Checklists