diff --git a/packages/utils/src/browser.ts b/packages/utils/src/browser.ts index c776796512a2..8181e3f11759 100644 --- a/packages/utils/src/browser.ts +++ b/packages/utils/src/browser.ts @@ -1,3 +1,4 @@ +import { getGlobalObject } from './global'; import { isString } from './is'; /** @@ -108,3 +109,15 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { } return out.join(''); } + +/** + * A safe form of location.href + */ +export function getLocationHref(): string { + const global = getGlobalObject(); + try { + return global.document.location.href; + } catch (oO) { + return ''; + } +} diff --git a/packages/utils/src/global.ts b/packages/utils/src/global.ts new file mode 100644 index 000000000000..4e63babf1dbd --- /dev/null +++ b/packages/utils/src/global.ts @@ -0,0 +1,44 @@ +/** + * NOTE: In order to avoid circular dependencies, if you add a function to this module and it needs to print something, + * you must either a) use `console.log` rather than the logger, or b) put your function elsewhere. + */ + +/* eslint-disable @typescript-eslint/no-explicit-any */ + +import { Integration } from '@sentry/types'; + +import { isNodeEnv } from './node'; + +/** Internal */ +interface SentryGlobal { + Sentry?: { + Integrations?: Integration[]; + }; + SENTRY_ENVIRONMENT?: string; + SENTRY_DSN?: string; + SENTRY_RELEASE?: { + id?: string; + }; + __SENTRY__: { + globalEventProcessors: any; + hub: any; + logger: any; + }; +} + +const fallbackGlobalObject = {}; + +/** + * Safely get global scope object + * + * @returns Global scope object + */ +export function getGlobalObject(): T & SentryGlobal { + return (isNodeEnv() + ? global + : typeof window !== 'undefined' // eslint-disable-line no-restricted-globals + ? window // eslint-disable-line no-restricted-globals + : typeof self !== 'undefined' + ? self + : fallbackGlobalObject) as T & SentryGlobal; +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts index da1983b5b7b6..3c5a3e61a480 100644 --- a/packages/utils/src/index.ts +++ b/packages/utils/src/index.ts @@ -2,6 +2,7 @@ export * from './async'; export * from './browser'; export * from './dsn'; export * from './error'; +export * from './global'; export * from './instrument'; export * from './is'; export * from './logger'; diff --git a/packages/utils/src/instrument.ts b/packages/utils/src/instrument.ts index 4079c4afbebc..f357fd212230 100644 --- a/packages/utils/src/instrument.ts +++ b/packages/utils/src/instrument.ts @@ -2,9 +2,9 @@ /* eslint-disable @typescript-eslint/ban-types */ import { WrappedFunction } from '@sentry/types'; +import { getGlobalObject } from './global'; import { isInstanceOf, isString } from './is'; import { logger } from './logger'; -import { getGlobalObject } from './misc'; import { fill } from './object'; import { getFunctionName } from './stacktrace'; import { supportsHistory, supportsNativeFetch } from './supports'; diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index ba804d860991..9c4d366ecb62 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -1,5 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { consoleSandbox, getGlobalObject } from './misc'; +import { WrappedFunction } from '@sentry/types'; + +import { getGlobalObject } from './global'; // TODO: Implement different loggers for different environments const global = getGlobalObject(); @@ -7,6 +9,50 @@ const global = getGlobalObject(); /** Prefix for logging strings */ const PREFIX = 'Sentry Logger '; +/** JSDoc */ +interface ExtensibleConsole extends Console { + [key: string]: any; +} + +/** + * Temporarily unwrap `console.log` and friends in order to perform the given callback using the original methods. + * Restores wrapping after the callback completes. + * + * @param callback The function to run against the original `console` messages + * @returns The results of the callback + */ +export function consoleSandbox(callback: () => any): any { + const global = getGlobalObject(); + const levels = ['debug', 'info', 'warn', 'error', 'log', 'assert']; + + if (!('console' in global)) { + return callback(); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + const originalConsole = (global as any).console as ExtensibleConsole; + const wrappedLevels: { [key: string]: any } = {}; + + // Restore all wrapped console methods + levels.forEach(level => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access + if (level in (global as any).console && (originalConsole[level] as WrappedFunction).__sentry_original__) { + wrappedLevels[level] = originalConsole[level] as WrappedFunction; + originalConsole[level] = (originalConsole[level] as WrappedFunction).__sentry_original__; + } + }); + + // Perform callback manipulations + const result = callback(); + + // Revert restoration to wrapped state + Object.keys(wrappedLevels).forEach(level => { + originalConsole[level] = wrappedLevels[level]; + }); + + return result; +} + /** JSDoc */ class Logger { /** JSDoc */ diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index 92145f671639..2793d24bc585 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -1,43 +1,9 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { Event, Integration, StackFrame, WrappedFunction } from '@sentry/types'; +import { Event, StackFrame } from '@sentry/types'; -import { isNodeEnv } from './node'; +import { getGlobalObject } from './global'; import { snipLine } from './string'; -/** Internal */ -interface SentryGlobal { - Sentry?: { - Integrations?: Integration[]; - }; - SENTRY_ENVIRONMENT?: string; - SENTRY_DSN?: string; - SENTRY_RELEASE?: { - id?: string; - }; - __SENTRY__: { - globalEventProcessors: any; - hub: any; - logger: any; - }; -} - -const fallbackGlobalObject = {}; - -/** - * Safely get global scope object - * - * @returns Global scope object - */ -export function getGlobalObject(): T & SentryGlobal { - return (isNodeEnv() - ? global - : typeof window !== 'undefined' // eslint-disable-line no-restricted-globals - ? window // eslint-disable-line no-restricted-globals - : typeof self !== 'undefined' - ? self - : fallbackGlobalObject) as T & SentryGlobal; -} - /** * Extended Window interface that allows for Crypto API usage in IE browsers */ @@ -143,44 +109,6 @@ export function getEventDescription(event: Event): string { return event.event_id || ''; } -/** JSDoc */ -interface ExtensibleConsole extends Console { - [key: string]: any; -} - -/** JSDoc */ -export function consoleSandbox(callback: () => any): any { - const global = getGlobalObject(); - const levels = ['debug', 'info', 'warn', 'error', 'log', 'assert']; - - if (!('console' in global)) { - return callback(); - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const originalConsole = (global as any).console as ExtensibleConsole; - const wrappedLevels: { [key: string]: any } = {}; - - // Restore all wrapped console methods - levels.forEach(level => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - if (level in (global as any).console && (originalConsole[level] as WrappedFunction).__sentry_original__) { - wrappedLevels[level] = originalConsole[level] as WrappedFunction; - originalConsole[level] = (originalConsole[level] as WrappedFunction).__sentry_original__; - } - }); - - // Perform callback manipulations - const result = callback(); - - // Revert restoration to wrapped state - Object.keys(wrappedLevels).forEach(level => { - originalConsole[level] = wrappedLevels[level]; - }); - - return result; -} - /** * Adds exception values, type and value to an synthetic Exception. * @param event The event to modify. @@ -223,18 +151,6 @@ export function addExceptionMechanism( } } -/** - * A safe form of location.href - */ -export function getLocationHref(): string { - const global = getGlobalObject(); - try { - return global.document.location.href; - } catch (oO) { - return ''; - } -} - // https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string const SEMVER_REGEXP = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; diff --git a/packages/utils/src/node.ts b/packages/utils/src/node.ts index 1b37e0867c07..18d29fc3144f 100644 --- a/packages/utils/src/node.ts +++ b/packages/utils/src/node.ts @@ -1,3 +1,8 @@ +/** + * NOTE: In order to avoid circular dependencies, if you add a function to this module and it needs to print something, + * you must either a) use `console.log` rather than the logger, or b) put your function elsewhere. + */ + /** * Checks whether we're in the Node.js or Browser environment * diff --git a/packages/utils/src/supports.ts b/packages/utils/src/supports.ts index 88e519b77a24..1a548b45f0d0 100644 --- a/packages/utils/src/supports.ts +++ b/packages/utils/src/supports.ts @@ -1,5 +1,5 @@ +import { getGlobalObject } from './global'; import { logger } from './logger'; -import { getGlobalObject } from './misc'; /** * Tells whether current environment supports ErrorEvent objects diff --git a/packages/utils/src/time.ts b/packages/utils/src/time.ts index 62beac716f02..932fffc902d9 100644 --- a/packages/utils/src/time.ts +++ b/packages/utils/src/time.ts @@ -1,4 +1,4 @@ -import { getGlobalObject } from './misc'; +import { getGlobalObject } from './global'; import { dynamicRequire, isNodeEnv } from './node'; /** diff --git a/packages/utils/test/global.test.ts b/packages/utils/test/global.test.ts new file mode 100644 index 000000000000..908cc59a9772 --- /dev/null +++ b/packages/utils/test/global.test.ts @@ -0,0 +1,12 @@ +import { getGlobalObject } from '../src/global'; + +describe('getGlobalObject()', () => { + test('should return the same object', () => { + const backup = global.process; + delete global.process; + const first = getGlobalObject(); + const second = getGlobalObject(); + expect(first).toEqual(second); + global.process = backup; + }); +}); diff --git a/packages/utils/test/misc.test.ts b/packages/utils/test/misc.test.ts index 72425032a06f..3faf4f955067 100644 --- a/packages/utils/test/misc.test.ts +++ b/packages/utils/test/misc.test.ts @@ -1,12 +1,6 @@ import { StackFrame } from '@sentry/types'; -import { - addContextToFrame, - getEventDescription, - getGlobalObject, - parseRetryAfterHeader, - stripUrlQueryAndFragment, -} from '../src/misc'; +import { addContextToFrame, getEventDescription, parseRetryAfterHeader, stripUrlQueryAndFragment } from '../src/misc'; describe('getEventDescription()', () => { test('message event', () => { @@ -117,17 +111,6 @@ describe('getEventDescription()', () => { }); }); -describe('getGlobalObject()', () => { - test('should return the same object', () => { - const backup = global.process; - delete global.process; - const first = getGlobalObject(); - const second = getGlobalObject(); - expect(first).toEqual(second); - global.process = backup; - }); -}); - describe('parseRetryAfterHeader', () => { test('no header', () => { expect(parseRetryAfterHeader(Date.now())).toEqual(60 * 1000);