Skip to content

Commit 6b3f553

Browse files
authored
fix(core): Make FunctionToString integration use SETUP_CLIENTS weakmap (#10358)
Auditing our integrations to make sure they work with the new client paradigm, also adding `export type` where appropriate.
1 parent adccbe6 commit 6b3f553

File tree

2 files changed

+62
-7
lines changed

2 files changed

+62
-7
lines changed

packages/core/src/integrations/functiontostring.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
import type { Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types';
1+
import type { Client, Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types';
22
import { getOriginalFunction } from '@sentry/utils';
3+
import { getClient } from '../exports';
34
import { convertIntegrationFnToClass, defineIntegration } from '../integration';
45

56
let originalFunctionToString: () => void;
67

78
const INTEGRATION_NAME = 'FunctionToString';
89

10+
const SETUP_CLIENTS = new WeakMap<Client, boolean>();
11+
912
const _functionToStringIntegration = (() => {
1013
return {
1114
name: INTEGRATION_NAME,
@@ -18,24 +21,44 @@ const _functionToStringIntegration = (() => {
1821
try {
1922
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2023
Function.prototype.toString = function (this: WrappedFunction, ...args: any[]): string {
21-
const context = getOriginalFunction(this) || this;
24+
const originalFunction = getOriginalFunction(this);
25+
const context =
26+
SETUP_CLIENTS.has(getClient() as Client) && originalFunction !== undefined ? originalFunction : this;
2227
return originalFunctionToString.apply(context, args);
2328
};
2429
} catch {
2530
// ignore errors here, just don't patch this
2631
}
2732
},
33+
setup(client) {
34+
SETUP_CLIENTS.set(client, true);
35+
},
2836
};
2937
}) satisfies IntegrationFn;
3038

39+
/**
40+
* Patch toString calls to return proper name for wrapped functions.
41+
*
42+
* ```js
43+
* Sentry.init({
44+
* integrations: [
45+
* functionToStringIntegration(),
46+
* ],
47+
* });
48+
* ```
49+
*/
3150
export const functionToStringIntegration = defineIntegration(_functionToStringIntegration);
3251

3352
/**
3453
* Patch toString calls to return proper name for wrapped functions.
54+
*
3555
* @deprecated Use `functionToStringIntegration()` instead.
3656
*/
3757
// eslint-disable-next-line deprecation/deprecation
3858
export const FunctionToString = convertIntegrationFnToClass(
3959
INTEGRATION_NAME,
4060
functionToStringIntegration,
4161
) as IntegrationClass<Integration & { setupOnce: () => void }>;
62+
63+
// eslint-disable-next-line deprecation/deprecation
64+
export type FunctionToString = typeof FunctionToString;

packages/core/test/lib/integrations/functiontostring.test.ts

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1-
import { fill } from '../../../../utils/src/object';
2-
import { FunctionToString } from '../../../src/integrations/functiontostring';
1+
import { fill } from '@sentry/utils';
2+
import { getClient, getCurrentScope, setCurrentClient } from '../../../src';
3+
import { functionToStringIntegration } from '../../../src/integrations/functiontostring';
4+
import { TestClient, getDefaultTestClientOptions } from '../../mocks/client';
35

46
describe('FunctionToString', () => {
7+
beforeEach(() => {
8+
const testClient = new TestClient(getDefaultTestClientOptions({}));
9+
setCurrentClient(testClient);
10+
});
11+
12+
afterAll(() => {
13+
getCurrentScope().setClient(undefined);
14+
});
15+
516
it('it works as expected', () => {
617
const foo = {
718
bar(wat: boolean): boolean {
@@ -17,10 +28,31 @@ describe('FunctionToString', () => {
1728

1829
expect(foo.bar.toString()).not.toBe(originalFunction);
1930

20-
// eslint-disable-next-line deprecation/deprecation
21-
const fts = new FunctionToString();
22-
fts.setupOnce();
31+
const fts = functionToStringIntegration();
32+
getClient()?.addIntegration?.(fts);
2333

2434
expect(foo.bar.toString()).toBe(originalFunction);
2535
});
36+
37+
it('does not activate when client is not active', () => {
38+
const foo = {
39+
bar(wat: boolean): boolean {
40+
return wat;
41+
},
42+
};
43+
const originalFunction = foo.bar.toString();
44+
fill(foo, 'bar', function wat(whatever: boolean): () => void {
45+
return function watwat(): boolean {
46+
return whatever;
47+
};
48+
});
49+
50+
expect(foo.bar.toString()).not.toBe(originalFunction);
51+
52+
const testClient = new TestClient(getDefaultTestClientOptions({}));
53+
const fts = functionToStringIntegration();
54+
testClient.addIntegration(fts);
55+
56+
expect(foo.bar.toString()).not.toBe(originalFunction);
57+
});
2658
});

0 commit comments

Comments
 (0)