Skip to content

Commit da62e90

Browse files
author
Luca Forstner
committed
Use Proxies
1 parent e8d3102 commit da62e90

File tree

1 file changed

+51
-43
lines changed
  • packages/browser-utils/src/instrument

1 file changed

+51
-43
lines changed

packages/browser-utils/src/instrument/xhr.ts

Lines changed: 51 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import type { HandlerDataXhr, SentryWrappedXMLHttpRequest, WrappedFunction } from '@sentry/types';
1+
import type { HandlerDataXhr, SentryWrappedXMLHttpRequest } from '@sentry/types';
22

3-
import { addHandler, fill, isString, maybeInstrument, timestampInSeconds, triggerHandlers } from '@sentry/utils';
3+
import { addHandler, isString, maybeInstrument, timestampInSeconds, triggerHandlers } from '@sentry/utils';
44
import { WINDOW } from '../types';
55

66
export const SENTRY_XHR_DATA_KEY = '__sentry_xhr_v3__';
@@ -29,108 +29,116 @@ export function instrumentXHR(): void {
2929

3030
const xhrproto = XMLHttpRequest.prototype;
3131

32-
fill(xhrproto, 'open', function (originalOpen: () => void): () => void {
33-
return function open(this: XMLHttpRequest & SentryWrappedXMLHttpRequest, ...args: unknown[]): void {
32+
// eslint-disable-next-line @typescript-eslint/unbound-method
33+
xhrproto.open = new Proxy(xhrproto.open, {
34+
apply(originalOpen, xhrOpenThisArg: XMLHttpRequest & SentryWrappedXMLHttpRequest, xhrOpenArgArray) {
3435
const startTimestamp = timestampInSeconds() * 1000;
3536

3637
// open() should always be called with two or more arguments
3738
// But to be on the safe side, we actually validate this and bail out if we don't have a method & url
38-
const method = isString(args[0]) ? args[0].toUpperCase() : undefined;
39-
const url = parseUrl(args[1]);
39+
const method = isString(xhrOpenArgArray[0]) ? xhrOpenArgArray[0].toUpperCase() : undefined;
40+
const url = parseUrl(xhrOpenArgArray[1]);
4041

4142
if (!method || !url) {
42-
return originalOpen.apply(this, args);
43+
return originalOpen.apply(xhrOpenThisArg, xhrOpenArgArray);
4344
}
4445

45-
this[SENTRY_XHR_DATA_KEY] = {
46+
xhrOpenThisArg[SENTRY_XHR_DATA_KEY] = {
4647
method,
4748
url,
4849
request_headers: {},
4950
};
5051

5152
// if Sentry key appears in URL, don't capture it as a request
5253
if (method === 'POST' && url.match(/sentry_key/)) {
53-
this.__sentry_own_request__ = true;
54+
xhrOpenThisArg.__sentry_own_request__ = true;
5455
}
5556

5657
const onreadystatechangeHandler: () => void = () => {
5758
// For whatever reason, this is not the same instance here as from the outer method
58-
const xhrInfo = this[SENTRY_XHR_DATA_KEY];
59+
const xhrInfo = xhrOpenThisArg[SENTRY_XHR_DATA_KEY];
5960

6061
if (!xhrInfo) {
6162
return;
6263
}
6364

64-
if (this.readyState === 4) {
65+
if (xhrOpenThisArg.readyState === 4) {
6566
try {
6667
// touching statusCode in some platforms throws
6768
// an exception
68-
xhrInfo.status_code = this.status;
69+
xhrInfo.status_code = xhrOpenThisArg.status;
6970
} catch (e) {
7071
/* do nothing */
7172
}
7273

7374
const handlerData: HandlerDataXhr = {
7475
endTimestamp: timestampInSeconds() * 1000,
7576
startTimestamp,
76-
xhr: this,
77+
xhr: xhrOpenThisArg,
7778
};
7879
triggerHandlers('xhr', handlerData);
7980
}
8081
};
8182

82-
if ('onreadystatechange' in this && typeof this.onreadystatechange === 'function') {
83-
fill(this, 'onreadystatechange', function (original: WrappedFunction) {
84-
return function onreadystatechange(this: SentryWrappedXMLHttpRequest, ...readyStateArgs: unknown[]): void {
83+
if ('onreadystatechange' in xhrOpenThisArg && typeof xhrOpenThisArg.onreadystatechange === 'function') {
84+
xhrOpenThisArg.onreadystatechange = new Proxy(xhrOpenThisArg.onreadystatechange, {
85+
apply(originalOnreadystatechange, onreadystatechangeThisArg, onreadystatechangeArgArray) {
8586
onreadystatechangeHandler();
86-
return original.apply(this, readyStateArgs);
87-
};
87+
return originalOnreadystatechange.apply(onreadystatechangeThisArg, onreadystatechangeArgArray);
88+
},
8889
});
8990
} else {
90-
this.addEventListener('readystatechange', onreadystatechangeHandler);
91+
xhrOpenThisArg.addEventListener('readystatechange', onreadystatechangeHandler);
9192
}
9293

9394
// Intercepting `setRequestHeader` to access the request headers of XHR instance.
9495
// This will only work for user/library defined headers, not for the default/browser-assigned headers.
9596
// Request cookies are also unavailable for XHR, as `Cookie` header can't be defined by `setRequestHeader`.
96-
fill(this, 'setRequestHeader', function (original: WrappedFunction) {
97-
return function setRequestHeader(this: SentryWrappedXMLHttpRequest, ...setRequestHeaderArgs: unknown[]): void {
98-
const [header, value] = setRequestHeaderArgs;
99-
100-
const xhrInfo = this[SENTRY_XHR_DATA_KEY];
101-
102-
if (xhrInfo && isString(header) && isString(value)) {
103-
xhrInfo.request_headers[header.toLowerCase()] = value;
104-
}
105-
106-
return original.apply(this, setRequestHeaderArgs);
107-
};
108-
});
97+
if ('setRequestHeader' in xhrOpenThisArg && typeof xhrOpenThisArg.onreadystatechange === 'function') {
98+
xhrOpenThisArg.setRequestHeader = new Proxy(xhrOpenThisArg.setRequestHeader, {
99+
apply(
100+
originalSetRequestHeader,
101+
setRequestHeaderThisArg: SentryWrappedXMLHttpRequest,
102+
setRequestHeaderArgArray,
103+
) {
104+
const [header, value] = setRequestHeaderArgArray;
105+
106+
const xhrInfo = setRequestHeaderThisArg[SENTRY_XHR_DATA_KEY];
107+
108+
if (xhrInfo && isString(header) && isString(value)) {
109+
xhrInfo.request_headers[header.toLowerCase()] = value;
110+
}
111+
112+
return originalSetRequestHeader.apply(setRequestHeaderThisArg, setRequestHeaderArgArray);
113+
},
114+
});
115+
}
109116

110-
return originalOpen.apply(this, args);
111-
};
117+
return originalOpen.apply(xhrOpenThisArg, xhrOpenArgArray);
118+
},
112119
});
113120

114-
fill(xhrproto, 'send', function (originalSend: () => void): () => void {
115-
return function send(this: XMLHttpRequest & SentryWrappedXMLHttpRequest, ...args: unknown[]): void {
116-
const sentryXhrData = this[SENTRY_XHR_DATA_KEY];
121+
// eslint-disable-next-line @typescript-eslint/unbound-method
122+
xhrproto.send = new Proxy(xhrproto.send, {
123+
apply(originalSend, sendThisArg: XMLHttpRequest & SentryWrappedXMLHttpRequest, sendArgArray: unknown[]) {
124+
const sentryXhrData = sendThisArg[SENTRY_XHR_DATA_KEY];
117125

118126
if (!sentryXhrData) {
119-
return originalSend.apply(this, args);
127+
return originalSend.apply(sendThisArg, sendArgArray);
120128
}
121129

122-
if (args[0] !== undefined) {
123-
sentryXhrData.body = args[0];
130+
if (sendArgArray[0] !== undefined) {
131+
sentryXhrData.body = sendArgArray[0];
124132
}
125133

126134
const handlerData: HandlerDataXhr = {
127135
startTimestamp: timestampInSeconds() * 1000,
128-
xhr: this,
136+
xhr: sendThisArg,
129137
};
130138
triggerHandlers('xhr', handlerData);
131139

132-
return originalSend.apply(this, args);
133-
};
140+
return originalSend.apply(sendThisArg, sendArgArray);
141+
},
134142
});
135143
}
136144

0 commit comments

Comments
 (0)