Skip to content

Commit 24ab73a

Browse files
committed
ref(types): Streamline wrap and related types
1 parent 3742bd3 commit 24ab73a

File tree

8 files changed

+107
-136
lines changed

8 files changed

+107
-136
lines changed

packages/browser/src/helpers.ts

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -40,29 +40,32 @@ export function ignoreNextOnError(): void {
4040
* @returns The wrapped function.
4141
* @hidden
4242
*/
43-
export function wrap(
44-
fn: WrappedFunction,
43+
// eslint-disable-next-line @typescript-eslint/ban-types
44+
export function wrap<T extends Function, NonFunctionType>(
45+
fn: T | NonFunctionType,
4546
options: {
4647
mechanism?: Mechanism;
4748
} = {},
48-
before?: WrappedFunction,
49-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
50-
): any {
49+
): NonFunctionType | WrappedFunction<T> {
5150
// for future readers what this does is wrap a function and then create
5251
// a bi-directional wrapping between them.
5352
//
5453
// example: wrapped = wrap(original);
5554
// original.__sentry_wrapped__ -> wrapped
5655
// wrapped.__sentry_original__ -> original
5756

58-
if (typeof fn !== 'function') {
57+
function isFunction(fn: T | NonFunctionType): fn is T {
58+
return typeof fn === 'function';
59+
}
60+
61+
if (!isFunction(fn)) {
5962
return fn;
6063
}
6164

6265
try {
6366
// if we're dealing with a function that was previously wrapped, return
6467
// the original wrapper.
65-
const wrapper = fn.__sentry_wrapped__;
68+
const wrapper = (fn as WrappedFunction<T>).__sentry_wrapped__;
6669
if (wrapper) {
6770
if (typeof wrapper === 'function') {
6871
return wrapper;
@@ -84,18 +87,12 @@ export function wrap(
8487
return fn;
8588
}
8689

87-
/* eslint-disable prefer-rest-params */
90+
// Wrap the function itself
8891
// 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-
92+
const sentryWrapped = function (this: unknown, ...args: unknown[]): unknown {
9293
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));
94+
// Also wrap arguments that are themselves functions
95+
const wrappedArguments = args.map((arg: unknown) => (typeof arg === 'function' ? wrap(arg, options) : arg));
9996

10097
// Attempt to invoke user-land function
10198
// NOTE: If you are a Sentry user, and you are seeing this stack frame, it
@@ -125,18 +122,18 @@ export function wrap(
125122

126123
throw ex;
127124
}
128-
};
129-
/* eslint-enable prefer-rest-params */
125+
} as unknown as WrappedFunction<T>;
130126

127+
// Wrap the wrapped function in a proxy, to ensure any other properties of the original function remain available
131128
// Accessing some objects may throw
132129
// ref: https://github.com/getsentry/sentry-javascript/issues/1168
133130
try {
134131
for (const property in fn) {
135132
if (Object.prototype.hasOwnProperty.call(fn, property)) {
136-
sentryWrapped[property] = fn[property];
133+
sentryWrapped[property as keyof T] = fn[property as keyof T];
137134
}
138135
}
139-
} catch (_oO) {} // eslint-disable-line no-empty
136+
} catch {} // eslint-disable-line no-empty
140137

141138
// Signal that this function has been wrapped/filled already
142139
// for both debugging and to prevent it to being wrapped/filled twice
@@ -146,16 +143,16 @@ export function wrap(
146143

147144
// Restore original function name (not all browsers allow that)
148145
try {
149-
const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name') as PropertyDescriptor;
146+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
147+
const descriptor = Object.getOwnPropertyDescriptor(sentryWrapped, 'name')!;
150148
if (descriptor.configurable) {
151149
Object.defineProperty(sentryWrapped, 'name', {
152150
get(): string {
153151
return fn.name;
154152
},
155153
});
156154
}
157-
// eslint-disable-next-line no-empty
158-
} catch (_oO) {}
155+
} catch {} // eslint-disable-line no-empty
159156

160157
return sentryWrapped;
161158
}

packages/browser/src/integrations/browserapierrors.ts

Lines changed: 51 additions & 64 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,13 +163,11 @@ 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

@@ -185,8 +177,7 @@ function _wrapEventTarget(target: string): void {
185177
options?: boolean | AddEventListenerOptions,
186178
) => void {
187179
return function (
188-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
189-
this: any,
180+
this: unknown,
190181
eventName: string,
191182
fn: EventListenerObject,
192183
options?: boolean | AddEventListenerOptions,
@@ -217,8 +208,7 @@ function _wrapEventTarget(target: string): void {
217208

218209
return original.apply(this, [
219210
eventName,
220-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
221-
wrap(fn as any as WrappedFunction, {
211+
wrap(fn, {
222212
mechanism: {
223213
data: {
224214
function: 'addEventListener',
@@ -234,48 +224,45 @@ function _wrapEventTarget(target: string): void {
234224
};
235225
});
236226

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

packages/browser/test/integrations/helpers.test.ts renamed to packages/browser/test/helpers.test.ts

Lines changed: 7 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { describe, expect, it, vi } from 'vitest';
22

33
import type { WrappedFunction } from '@sentry/types';
44

5-
import { wrap } from '../../src/helpers';
5+
import { wrap } from '../src/helpers';
66

77
describe('internal wrap()', () => {
88
it('should wrap only functions', () => {
@@ -13,13 +13,9 @@ describe('internal wrap()', () => {
1313
const num = 42;
1414

1515
expect(wrap(fn)).not.toBe(fn);
16-
// @ts-expect-error Issue with `WrappedFunction` type from wrap fn
1716
expect(wrap(obj)).toBe(obj);
18-
// @ts-expect-error Issue with `WrappedFunction` type from wrap fn
1917
expect(wrap(arr)).toBe(arr);
20-
// @ts-expect-error Issue with `WrappedFunction` type from wrap fn
2118
expect(wrap(str)).toBe(str);
22-
// @ts-expect-error Issue with `WrappedFunction` type from wrap fn
2319
expect(wrap(num)).toBe(num);
2420
});
2521

@@ -56,16 +52,6 @@ describe('internal wrap()', () => {
5652
expect(wrap(wrapped)).toBe(wrapped);
5753
});
5854

59-
it('calls "before" function when invoking wrapped function', () => {
60-
const fn = (() => 1337) as WrappedFunction;
61-
const before = vi.fn();
62-
63-
const wrapped = wrap(fn, {}, before);
64-
wrapped();
65-
66-
expect(before).toHaveBeenCalledTimes(1);
67-
});
68-
6955
it('attaches metadata to original and wrapped functions', () => {
7056
const fn = (() => 1337) as WrappedFunction;
7157

@@ -78,10 +64,11 @@ describe('internal wrap()', () => {
7864
expect(wrapped.__sentry_original__).toBe(fn);
7965
});
8066

81-
it('copies over original functions properties', () => {
82-
const fn = (() => 1337) as WrappedFunction;
83-
fn.some = 1337;
84-
fn.property = 'Rick';
67+
it('keeps original functions properties', () => {
68+
const fn = Object.assign(() => 1337, {
69+
some: 1337,
70+
property: 'Rick',
71+
});
8572

8673
const wrapped = wrap(fn);
8774

@@ -105,7 +92,7 @@ describe('internal wrap()', () => {
10592
});
10693

10794
it('recrusively wraps arguments that are functions', () => {
108-
const fn = (() => 1337) as WrappedFunction;
95+
const fn = (_arg1: unknown, _arg2: unknown) => 1337;
10996
const fnArgA = (): number => 1337;
11097
const fnArgB = (): number => 1337;
11198

packages/core/src/utils-hoist/object.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,8 @@ export function markFunctionWrapped(wrapped: WrappedFunction, original: WrappedF
8181
* @param func the function to unwrap
8282
* @returns the unwrapped version of the function if available.
8383
*/
84-
export function getOriginalFunction(func: WrappedFunction): WrappedFunction | undefined {
84+
// eslint-disable-next-line @typescript-eslint/ban-types
85+
export function getOriginalFunction<T extends Function>(func: WrappedFunction<T>): T | undefined {
8586
return func.__sentry_original__;
8687
}
8788

packages/google-cloud-serverless/src/gcpfunction/events.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,15 @@ export function wrapEventFunction(
2222
return proxyFunction(fn, f => domainify(_wrapEventFunction(f, wrapOptions)));
2323
}
2424

25-
/** */
2625
function _wrapEventFunction<F extends EventFunction | EventFunctionWithCallback>(
2726
fn: F,
2827
wrapOptions: Partial<EventFunctionWrapperOptions> = {},
29-
): (...args: Parameters<F>) => ReturnType<F> | Promise<void> {
28+
): (...args: Parameters<F>) => void | Promise<void> {
3029
const options: EventFunctionWrapperOptions = {
3130
flushTimeout: 2000,
3231
...wrapOptions,
3332
};
34-
return (...eventFunctionArguments: Parameters<F>): ReturnType<F> | Promise<void> => {
33+
return (...eventFunctionArguments: Parameters<F>): void | Promise<void> => {
3534
const [data, context, callback] = eventFunctionArguments;
3635

3736
return startSpanManual(
@@ -47,8 +46,8 @@ function _wrapEventFunction<F extends EventFunction | EventFunctionWithCallback>
4746
const scope = getCurrentScope();
4847
scope.setContext('gcp.function.context', { ...context });
4948

50-
const newCallback = domainify((...args: unknown[]) => {
51-
if (args[0] !== null && args[0] !== undefined) {
49+
const newCallback = domainify((...args): void => {
50+
if (args[0] != null) {
5251
captureException(args[0], scope => markEventUnhandled(scope));
5352
}
5453
span.end();

0 commit comments

Comments
 (0)