diff --git a/packages/core/src/integrations/functiontostring.ts b/packages/core/src/integrations/functiontostring.ts index 7a50aec67f2d..0f3e9f08b59e 100644 --- a/packages/core/src/integrations/functiontostring.ts +++ b/packages/core/src/integrations/functiontostring.ts @@ -1,11 +1,14 @@ -import type { Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types'; +import type { Client, Integration, IntegrationClass, IntegrationFn, WrappedFunction } from '@sentry/types'; import { getOriginalFunction } from '@sentry/utils'; +import { getClient } from '../exports'; import { convertIntegrationFnToClass, defineIntegration } from '../integration'; let originalFunctionToString: () => void; const INTEGRATION_NAME = 'FunctionToString'; +const SETUP_CLIENTS = new WeakMap(); + const _functionToStringIntegration = (() => { return { name: INTEGRATION_NAME, @@ -18,20 +21,37 @@ const _functionToStringIntegration = (() => { try { // eslint-disable-next-line @typescript-eslint/no-explicit-any Function.prototype.toString = function (this: WrappedFunction, ...args: any[]): string { - const context = getOriginalFunction(this) || this; + const originalFunction = getOriginalFunction(this); + const context = + SETUP_CLIENTS.has(getClient() as Client) && originalFunction !== undefined ? originalFunction : this; return originalFunctionToString.apply(context, args); }; } catch { // ignore errors here, just don't patch this } }, + setup(client) { + SETUP_CLIENTS.set(client, true); + }, }; }) satisfies IntegrationFn; +/** + * Patch toString calls to return proper name for wrapped functions. + * + * ```js + * Sentry.init({ + * integrations: [ + * functionToStringIntegration(), + * ], + * }); + * ``` + */ export const functionToStringIntegration = defineIntegration(_functionToStringIntegration); /** * Patch toString calls to return proper name for wrapped functions. + * * @deprecated Use `functionToStringIntegration()` instead. */ // eslint-disable-next-line deprecation/deprecation @@ -39,3 +59,6 @@ export const FunctionToString = convertIntegrationFnToClass( INTEGRATION_NAME, functionToStringIntegration, ) as IntegrationClass void }>; + +// eslint-disable-next-line deprecation/deprecation +export type FunctionToString = typeof FunctionToString; diff --git a/packages/core/test/lib/integrations/functiontostring.test.ts b/packages/core/test/lib/integrations/functiontostring.test.ts index bb3b62d11915..c0e2a22cd6ed 100644 --- a/packages/core/test/lib/integrations/functiontostring.test.ts +++ b/packages/core/test/lib/integrations/functiontostring.test.ts @@ -1,7 +1,18 @@ -import { fill } from '../../../../utils/src/object'; -import { FunctionToString } from '../../../src/integrations/functiontostring'; +import { fill } from '@sentry/utils'; +import { getClient, getCurrentScope, setCurrentClient } from '../../../src'; +import { functionToStringIntegration } from '../../../src/integrations/functiontostring'; +import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; describe('FunctionToString', () => { + beforeEach(() => { + const testClient = new TestClient(getDefaultTestClientOptions({})); + setCurrentClient(testClient); + }); + + afterAll(() => { + getCurrentScope().setClient(undefined); + }); + it('it works as expected', () => { const foo = { bar(wat: boolean): boolean { @@ -17,10 +28,31 @@ describe('FunctionToString', () => { expect(foo.bar.toString()).not.toBe(originalFunction); - // eslint-disable-next-line deprecation/deprecation - const fts = new FunctionToString(); - fts.setupOnce(); + const fts = functionToStringIntegration(); + getClient()?.addIntegration?.(fts); expect(foo.bar.toString()).toBe(originalFunction); }); + + it('does not activate when client is not active', () => { + const foo = { + bar(wat: boolean): boolean { + return wat; + }, + }; + const originalFunction = foo.bar.toString(); + fill(foo, 'bar', function wat(whatever: boolean): () => void { + return function watwat(): boolean { + return whatever; + }; + }); + + expect(foo.bar.toString()).not.toBe(originalFunction); + + const testClient = new TestClient(getDefaultTestClientOptions({})); + const fts = functionToStringIntegration(); + testClient.addIntegration(fts); + + expect(foo.bar.toString()).not.toBe(originalFunction); + }); });