Skip to content

Commit 4a4b4a3

Browse files
committed
core: add wrapMethod/unwrapMethod util
1 parent 8808ea1 commit 4a4b4a3

File tree

2 files changed

+68
-0
lines changed

2 files changed

+68
-0
lines changed

packages/core/src/utils/object.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { isElement, isError, isEvent, isInstanceOf, isPrimitive } from './is';
1818
* args>)` or `origMethod.apply(this, [<other args>])` (rather than being called directly), again to preserve `this`.
1919
* @returns void
2020
*/
21+
2122
export function fill(source: { [key: string]: any }, name: string, replacementFactory: (...args: any[]) => any): void {
2223
if (!(name in source)) {
2324
return;
@@ -80,6 +81,36 @@ export function markFunctionWrapped(wrapped: WrappedFunction, original: WrappedF
8081
} catch {} // eslint-disable-line no-empty
8182
}
8283

84+
/**
85+
* Wrap a method on an object by name, only if it is not already wrapped.
86+
*/
87+
export function wrapMethod<O extends {}, T extends string & keyof O>(obj: O, field: T, wrapped: WrappedFunction): void {
88+
const original = obj[field];
89+
if (typeof original !== 'function') {
90+
throw new Error(`Cannot wrap method: ${field} is not a function`);
91+
}
92+
if (getOriginalFunction(original)) {
93+
throw new Error(`Attempting to wrap method ${field} multiple times`);
94+
}
95+
markFunctionWrapped(wrapped, original);
96+
addNonEnumerableProperty(obj, field, wrapped);
97+
}
98+
99+
/**
100+
* Unwrap a previously wrapped method on an object by name
101+
*/
102+
export function unwrapMethod<O extends {}, T extends string & keyof O>(obj: O, field: T): void {
103+
const wrapped = obj[field];
104+
if (typeof wrapped !== 'function') {
105+
throw new Error(`Cannot unwrap method: ${field} is not a function`);
106+
}
107+
const original = getOriginalFunction(wrapped);
108+
if (!original) {
109+
throw new Error(`Method ${field} is not wrapped, and cannot be unwrapped`);
110+
}
111+
addNonEnumerableProperty(obj, field, original);
112+
}
113+
83114
/**
84115
* This extracts the original function if available. See
85116
* `markFunctionWrapped` for more information.

packages/core/test/lib/utils/object.test.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import {
1111
fill,
1212
markFunctionWrapped,
1313
objectify,
14+
unwrapMethod,
15+
wrapMethod,
1416
} from '../../../src/utils/object';
1517
import { testOnlyIfNodeVersionAtLeast } from '../../testutils';
1618

@@ -455,3 +457,38 @@ describe('markFunctionWrapped', () => {
455457
expect(originalFunc).not.toHaveBeenCalled();
456458
});
457459
});
460+
461+
describe('unwrapMethod, wrapMethod', () => {
462+
it('can wrap a method on an object and unwrap it later', () => {
463+
const wrapped = () => {};
464+
const original = () => {};
465+
const obj = { m: original };
466+
wrapMethod(obj, 'm', wrapped);
467+
expect(obj.m).toBe(wrapped);
468+
expect((obj.m as WrappedFunction).__sentry_original__).toBe(original);
469+
unwrapMethod(obj, 'm');
470+
expect(obj.m).toBe(original);
471+
});
472+
473+
it('throws if misused', () => {
474+
const wrapped = () => {};
475+
const original = () => {};
476+
const obj = { m: original };
477+
wrapMethod(obj, 'm', wrapped);
478+
expect(() => {
479+
//@ts-expect-error verify type checking prevents this mistake
480+
wrapMethod(obj, 'foo', wrapped);
481+
}).toThrowError('Cannot wrap method: foo is not a function');
482+
expect(() => {
483+
//@ts-expect-error verify type checking prevents this mistake
484+
unwrapMethod(obj, 'foo');
485+
}).toThrowError('Cannot unwrap method: foo is not a function');
486+
expect(() => {
487+
wrapMethod(obj, 'm', wrapped);
488+
}).toThrowError('Attempting to wrap method m multiple times');
489+
unwrapMethod(obj, 'm');
490+
expect(() => {
491+
unwrapMethod(obj, 'm');
492+
}).toThrowError('Method m is not wrapped, and cannot be unwrapped');
493+
});
494+
});

0 commit comments

Comments
 (0)