Skip to content

Commit 4d218ad

Browse files
committed
ref(types): Avoid some any type casting around wrap code
1 parent fc1d986 commit 4d218ad

File tree

5 files changed

+126
-126
lines changed

5 files changed

+126
-126
lines changed

packages/browser/src/helpers.ts

Lines changed: 41 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ export function ignoreNextOnError(): void {
3131
});
3232
}
3333

34+
// eslint-disable-next-line @typescript-eslint/ban-types
35+
type WrappableFunction = Function;
36+
37+
export function wrap<T extends WrappableFunction>(
38+
fn: T,
39+
options?: {
40+
mechanism?: Mechanism;
41+
},
42+
): WrappedFunction<T>;
43+
export function wrap<NonFunction>(
44+
fn: NonFunction,
45+
options?: {
46+
mechanism?: Mechanism;
47+
},
48+
): NonFunction;
3449
/**
3550
* Instruments the given function and sends an event to Sentry every time the
3651
* function throws an exception.
@@ -40,29 +55,31 @@ export function ignoreNextOnError(): void {
4055
* @returns The wrapped function.
4156
* @hidden
4257
*/
43-
export function wrap(
44-
fn: WrappedFunction,
58+
export function wrap<T extends WrappableFunction, NonFunction>(
59+
fn: T | NonFunction,
4560
options: {
4661
mechanism?: Mechanism;
4762
} = {},
48-
before?: WrappedFunction,
49-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
50-
): any {
63+
): NonFunction | WrappedFunction<T> {
5164
// for future readers what this does is wrap a function and then create
5265
// a bi-directional wrapping between them.
5366
//
5467
// example: wrapped = wrap(original);
5568
// original.__sentry_wrapped__ -> wrapped
5669
// wrapped.__sentry_original__ -> original
5770

58-
if (typeof fn !== 'function') {
71+
function isFunction(fn: T | NonFunction): fn is T {
72+
return typeof fn === 'function';
73+
}
74+
75+
if (!isFunction(fn)) {
5976
return fn;
6077
}
6178

6279
try {
6380
// if we're dealing with a function that was previously wrapped, return
6481
// the original wrapper.
65-
const wrapper = fn.__sentry_wrapped__;
82+
const wrapper = (fn as WrappedFunction<T>).__sentry_wrapped__;
6683
if (wrapper) {
6784
if (typeof wrapper === 'function') {
6885
return wrapper;
@@ -84,18 +101,12 @@ export function wrap(
84101
return fn;
85102
}
86103

87-
/* eslint-disable prefer-rest-params */
104+
// Wrap the function itself
88105
// It is important that `sentryWrapped` is not an arrow function to preserve the context of `this`
89-
const sentryWrapped: WrappedFunction = function (this: unknown): void {
90-
const args = Array.prototype.slice.call(arguments);
91-
106+
const sentryWrapped = function (this: unknown, ...args: unknown[]): unknown {
92107
try {
93-
if (before && typeof before === 'function') {
94-
before.apply(this, arguments);
95-
}
96-
97-
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
98-
const wrappedArguments = args.map((arg: any) => wrap(arg, options));
108+
// Also wrap arguments that are themselves functions
109+
const wrappedArguments = args.map(arg => wrap(arg, options));
99110

100111
// Attempt to invoke user-land function
101112
// NOTE: If you are a Sentry user, and you are seeing this stack frame, it
@@ -125,18 +136,19 @@ export function wrap(
125136

126137
throw ex;
127138
}
128-
};
129-
/* eslint-enable prefer-rest-params */
139+
} as unknown as WrappedFunction<T>;
130140

131-
// Accessing some objects may throw
132-
// ref: https://github.com/getsentry/sentry-javascript/issues/1168
141+
// Wrap the wrapped function in a proxy, to ensure any other properties of the original function remain available
133142
try {
134143
for (const property in fn) {
135144
if (Object.prototype.hasOwnProperty.call(fn, property)) {
136-
sentryWrapped[property] = fn[property];
145+
sentryWrapped[property as keyof T] = fn[property as keyof T];
137146
}
138147
}
139-
} catch (_oO) {} // eslint-disable-line no-empty
148+
} catch {
149+
// Accessing some objects may throw
150+
// ref: https://github.com/getsentry/sentry-javascript/issues/1168
151+
}
140152

141153
// Signal that this function has been wrapped/filled already
142154
// for both debugging and to prevent it to being wrapped/filled twice
@@ -146,16 +158,19 @@ export function wrap(
146158

147159
// Restore original function name (not all browsers allow that)
148160
try {
149-
const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name') as PropertyDescriptor;
161+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
162+
const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name')!;
150163
if (descriptor.configurable) {
151164
Object.defineProperty(sentryWrapped, 'name', {
152165
get(): string {
153166
return fn.name;
154167
},
155168
});
156169
}
157-
// eslint-disable-next-line no-empty
158-
} catch (_oO) {}
170+
} catch {
171+
// This may throw if e.g. the descriptor does not exist, or a browser does not allow redefining `name`.
172+
// to save some bytes we simply try-catch this
173+
}
159174

160175
return sentryWrapped;
161176
}

packages/browser/src/integrations/browserapierrors.ts

Lines changed: 53 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@ const _browserApiErrorsIntegration = ((options: Partial<BrowserApiErrorsOptions>
9696
export const browserApiErrorsIntegration = defineIntegration(_browserApiErrorsIntegration);
9797

9898
function _wrapTimeFunction(original: () => void): () => number {
99-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
100-
return function (this: any, ...args: any[]): number {
99+
return function (this: unknown, ...args: unknown[]): number {
101100
const originalCallback = args[0];
102101
args[0] = wrap(originalCallback, {
103102
mechanism: {
@@ -110,11 +109,8 @@ function _wrapTimeFunction(original: () => void): () => number {
110109
};
111110
}
112111

113-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
114-
function _wrapRAF(original: any): (callback: () => void) => any {
115-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
116-
return function (this: any, callback: () => void): () => void {
117-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
112+
function _wrapRAF(original: () => void): (callback: () => void) => unknown {
113+
return function (this: unknown, callback: () => void): () => void {
118114
return original.apply(this, [
119115
wrap(callback, {
120116
mechanism: {
@@ -131,16 +127,14 @@ function _wrapRAF(original: any): (callback: () => void) => any {
131127
}
132128

133129
function _wrapXHR(originalSend: () => void): () => void {
134-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
135-
return function (this: XMLHttpRequest, ...args: any[]): void {
130+
return function (this: XMLHttpRequest, ...args: unknown[]): void {
136131
// eslint-disable-next-line @typescript-eslint/no-this-alias
137132
const xhr = this;
138133
const xmlHttpRequestProps: XMLHttpRequestProp[] = ['onload', 'onerror', 'onprogress', 'onreadystatechange'];
139134

140135
xmlHttpRequestProps.forEach(prop => {
141136
if (prop in xhr && typeof xhr[prop] === 'function') {
142-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
143-
fill(xhr, prop, function (original: WrappedFunction): () => any {
137+
fill(xhr, prop, function (original) {
144138
const wrapOptions = {
145139
mechanism: {
146140
data: {
@@ -169,30 +163,25 @@ function _wrapXHR(originalSend: () => void): () => void {
169163
}
170164

171165
function _wrapEventTarget(target: string): void {
172-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
173-
const globalObject = WINDOW as { [key: string]: any };
174-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
175-
const proto = globalObject[target] && globalObject[target].prototype;
166+
const globalObject = WINDOW as unknown as Record<string, { prototype?: object }>;
167+
const targetObj = globalObject[target];
168+
const proto = targetObj && targetObj.prototype;
176169

177-
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins
178-
if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {
170+
if (!proto || Object.prototype.hasOwnProperty.call(proto, 'addEventListener')) {
179171
return;
180172
}
181173

182174
fill(proto, 'addEventListener', function (original: VoidFunction,): (
183-
eventName: string,
184-
fn: EventListenerObject,
185-
options?: boolean | AddEventListenerOptions,
175+
...args: Parameters<typeof WINDOW.addEventListener>
186176
) => void {
187177
return function (
188-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
189-
this: any,
190-
eventName: string,
191-
fn: EventListenerObject,
192-
options?: boolean | AddEventListenerOptions,
178+
this: unknown,
179+
eventName,
180+
fn,
181+
options,
193182
): (eventName: string, fn: EventListenerObject, capture?: boolean, secure?: boolean) => void {
194183
try {
195-
if (typeof fn.handleEvent === 'function') {
184+
if (isEventListenerObject(fn)) {
196185
// ESlint disable explanation:
197186
// First, it is generally safe to call `wrap` with an unbound function. Furthermore, using `.bind()` would
198187
// introduce a bug here, because bind returns a new function that doesn't have our
@@ -211,14 +200,13 @@ function _wrapEventTarget(target: string): void {
211200
},
212201
});
213202
}
214-
} catch (err) {
203+
} catch {
215204
// can sometimes get 'Permission denied to access property "handle Event'
216205
}
217206

218207
return original.apply(this, [
219208
eventName,
220-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
221-
wrap(fn as any as WrappedFunction, {
209+
wrap(fn, {
222210
mechanism: {
223211
data: {
224212
function: 'addEventListener',
@@ -234,48 +222,41 @@ function _wrapEventTarget(target: string): void {
234222
};
235223
});
236224

237-
fill(
238-
proto,
239-
'removeEventListener',
240-
function (
241-
originalRemoveEventListener: () => void,
242-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
243-
): (this: any, eventName: string, fn: EventListenerObject, options?: boolean | EventListenerOptions) => () => void {
244-
return function (
245-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
246-
this: any,
247-
eventName: string,
248-
fn: EventListenerObject,
249-
options?: boolean | EventListenerOptions,
250-
): () => void {
251-
/**
252-
* There are 2 possible scenarios here:
253-
*
254-
* 1. Someone passes a callback, which was attached prior to Sentry initialization, or by using unmodified
255-
* method, eg. `document.addEventListener.call(el, name, handler). In this case, we treat this function
256-
* as a pass-through, and call original `removeEventListener` with it.
257-
*
258-
* 2. Someone passes a callback, which was attached after Sentry was initialized, which means that it was using
259-
* our wrapped version of `addEventListener`, which internally calls `wrap` helper.
260-
* This helper "wraps" whole callback inside a try/catch statement, and attached appropriate metadata to it,
261-
* in order for us to make a distinction between wrapped/non-wrapped functions possible.
262-
* If a function was wrapped, it has additional property of `__sentry_wrapped__`, holding the handler.
263-
*
264-
* When someone adds a handler prior to initialization, and then do it again, but after,
265-
* then we have to detach both of them. Otherwise, if we'd detach only wrapped one, it'd be impossible
266-
* to get rid of the initial handler and it'd stick there forever.
267-
*/
268-
const wrappedEventHandler = fn as unknown as WrappedFunction;
269-
try {
270-
const originalEventHandler = wrappedEventHandler && wrappedEventHandler.__sentry_wrapped__;
271-
if (originalEventHandler) {
272-
originalRemoveEventListener.call(this, eventName, originalEventHandler, options);
273-
}
274-
} catch (e) {
275-
// ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
225+
fill(proto, 'removeEventListener', function (originalRemoveEventListener: () => void,): (
226+
this: unknown,
227+
...args: Parameters<typeof WINDOW.removeEventListener>
228+
) => () => void {
229+
return function (this: unknown, eventName, fn, options): () => void {
230+
/**
231+
* There are 2 possible scenarios here:
232+
*
233+
* 1. Someone passes a callback, which was attached prior to Sentry initialization, or by using unmodified
234+
* method, eg. `document.addEventListener.call(el, name, handler). In this case, we treat this function
235+
* as a pass-through, and call original `removeEventListener` with it.
236+
*
237+
* 2. Someone passes a callback, which was attached after Sentry was initialized, which means that it was using
238+
* our wrapped version of `addEventListener`, which internally calls `wrap` helper.
239+
* This helper "wraps" whole callback inside a try/catch statement, and attached appropriate metadata to it,
240+
* in order for us to make a distinction between wrapped/non-wrapped functions possible.
241+
* If a function was wrapped, it has additional property of `__sentry_wrapped__`, holding the handler.
242+
*
243+
* When someone adds a handler prior to initialization, and then do it again, but after,
244+
* then we have to detach both of them. Otherwise, if we'd detach only wrapped one, it'd be impossible
245+
* to get rid of the initial handler and it'd stick there forever.
246+
*/
247+
try {
248+
const originalEventHandler = (fn as WrappedFunction).__sentry_wrapped__;
249+
if (originalEventHandler) {
250+
originalRemoveEventListener.call(this, eventName, originalEventHandler, options);
276251
}
277-
return originalRemoveEventListener.call(this, eventName, wrappedEventHandler, options);
278-
};
279-
},
280-
);
252+
} catch (e) {
253+
// ignore, accessing __sentry_wrapped__ will throw in some Selenium environments
254+
}
255+
return originalRemoveEventListener.call(this, eventName, fn, options);
256+
};
257+
});
258+
}
259+
260+
function isEventListenerObject(obj: unknown): obj is EventListenerObject {
261+
return typeof (obj as EventListenerObject).handleEvent === 'function';
281262
}

0 commit comments

Comments
 (0)