From 99a068775f06049f8e4b29e38645a926905a6680 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Tue, 27 Sep 2022 12:35:47 +0100 Subject: [PATCH] Remove `getGlobalObject` usage from utils --- packages/utils/src/browser.ts | 15 +++++---- packages/utils/src/global.ts | 28 +++++++++++------ packages/utils/src/instrument.ts | 50 ++++++++++++++---------------- packages/utils/src/logger.ts | 15 +++------ packages/utils/src/misc.ts | 21 ++++++------- packages/utils/src/supports.ts | 17 +++++----- packages/utils/src/time.ts | 6 ++-- packages/utils/test/global.test.ts | 8 ++--- 8 files changed, 80 insertions(+), 80 deletions(-) diff --git a/packages/utils/src/browser.ts b/packages/utils/src/browser.ts index 62d38077054b..30cd546c6ad8 100644 --- a/packages/utils/src/browser.ts +++ b/packages/utils/src/browser.ts @@ -1,6 +1,11 @@ -import { getGlobalObject } from './global'; +import { GLOBAL_OBJ } from './global'; import { isString } from './is'; +/** + * TODO: Move me to @sentry/browser when @sentry/utils no longer contains any browser code + */ +export const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window; + /** * Given a child DOM element, returns a query-selector statement describing that * and its ancestors @@ -115,9 +120,8 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string { * A safe form of location.href */ export function getLocationHref(): string { - const global = getGlobalObject(); try { - return global.document.location.href; + return WINDOW.document.location.href; } catch (oO) { return ''; } @@ -141,9 +145,8 @@ export function getLocationHref(): string { */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export function getDomElement(selector: string): E | null { - const global = getGlobalObject(); - if (global.document && global.document.querySelector) { - return global.document.querySelector(selector) as unknown as E; + if (WINDOW.document && WINDOW.document.querySelector) { + return WINDOW.document.querySelector(selector) as unknown as E; } return null; } diff --git a/packages/utils/src/global.ts b/packages/utils/src/global.ts index 8515e2af82eb..d32860fc75be 100644 --- a/packages/utils/src/global.ts +++ b/packages/utils/src/global.ts @@ -7,8 +7,10 @@ import { Integration } from '@sentry/types'; -/** Internal */ -interface SentryGlobal { +/** Internal global with common properties and Sentry extensions */ +export interface InternalGlobal { + navigator?: { userAgent?: string }; + console: Console; Sentry?: { Integrations?: Integration[]; }; @@ -21,10 +23,15 @@ interface SentryGlobal { globalEventProcessors: any; hub: any; logger: any; + extensions?: { + /** Extension methods for the hub, which are bound to the current Hub instance */ + // eslint-disable-next-line @typescript-eslint/ban-types + [key: string]: Function; + }; }; } -// The code below for 'isGlobalObj' and 'GLOBAL' was copied from core-js before modification +// The code below for 'isGlobalObj' and 'GLOBAL_OBJ' was copied from core-js before modification // https://github.com/zloirock/core-js/blob/1b944df55282cdc99c90db5f49eb0b6eda2cc0a3/packages/core-js/internals/global.js // core-js has the following licence: // @@ -53,7 +60,8 @@ function isGlobalObj(obj: { Math?: Math }): any | undefined { return obj && obj.Math == Math ? obj : undefined; } -const GLOBAL = +/** Get's the global object for the current JavaScript runtime */ +export const GLOBAL_OBJ: InternalGlobal = (typeof globalThis == 'object' && isGlobalObj(globalThis)) || // eslint-disable-next-line no-restricted-globals (typeof window == 'object' && isGlobalObj(window)) || @@ -69,8 +77,8 @@ const GLOBAL = * * @returns Global scope object */ -export function getGlobalObject(): T & SentryGlobal { - return GLOBAL as T & SentryGlobal; +export function getGlobalObject(): T & InternalGlobal { + return GLOBAL_OBJ as T & InternalGlobal; } /** @@ -81,12 +89,12 @@ export function getGlobalObject(): T & SentryGlobal { * * @param name name of the global singleton on __SENTRY__ * @param creator creator Factory function to create the singleton if it doesn't already exist on `__SENTRY__` - * @param obj (Optional) The global object on which to look for `__SENTRY__`, if not `getGlobalObject`'s return value + * @param obj (Optional) The global object on which to look for `__SENTRY__`, if not `GLOBAL_OBJ`'s return value * @returns the singleton */ -export function getGlobalSingleton(name: keyof SentryGlobal['__SENTRY__'], creator: () => T, obj?: unknown): T { - const global = (obj || GLOBAL) as SentryGlobal; - const __SENTRY__ = (global.__SENTRY__ = global.__SENTRY__ || {}); +export function getGlobalSingleton(name: keyof InternalGlobal['__SENTRY__'], creator: () => T, obj?: unknown): T { + const gbl = (obj || GLOBAL_OBJ) as InternalGlobal; + const __SENTRY__ = (gbl.__SENTRY__ = gbl.__SENTRY__ || {}); const singleton = __SENTRY__[name] || (__SENTRY__[name] = creator()); return singleton; } diff --git a/packages/utils/src/instrument.ts b/packages/utils/src/instrument.ts index 05a2a0abdf3c..12dcfae9ac4e 100644 --- a/packages/utils/src/instrument.ts +++ b/packages/utils/src/instrument.ts @@ -3,15 +3,13 @@ /* eslint-disable @typescript-eslint/ban-types */ import { WrappedFunction } from '@sentry/types'; -import { getGlobalObject } from './global'; +import { WINDOW } from './browser'; import { isInstanceOf, isString } from './is'; import { CONSOLE_LEVELS, logger } from './logger'; import { fill } from './object'; import { getFunctionName } from './stacktrace'; import { supportsHistory, supportsNativeFetch } from './supports'; -const global = getGlobalObject(); - export type InstrumentHandlerType = | 'console' | 'dom' @@ -105,22 +103,22 @@ function triggerHandlers(type: InstrumentHandlerType, data: any): void { /** JSDoc */ function instrumentConsole(): void { - if (!('console' in global)) { + if (!('console' in WINDOW)) { return; } CONSOLE_LEVELS.forEach(function (level: string): void { - if (!(level in global.console)) { + if (!(level in WINDOW.console)) { return; } - fill(global.console, level, function (originalConsoleMethod: () => any): Function { + fill(WINDOW.console, level, function (originalConsoleMethod: () => any): Function { return function (...args: any[]): void { triggerHandlers('console', { args, level }); // this fails for some browsers. :( if (originalConsoleMethod) { - originalConsoleMethod.apply(global.console, args); + originalConsoleMethod.apply(WINDOW.console, args); } }; }); @@ -133,7 +131,7 @@ function instrumentFetch(): void { return; } - fill(global, 'fetch', function (originalFetch: () => void): () => void { + fill(WINDOW, 'fetch', function (originalFetch: () => void): () => void { return function (...args: any[]): void { const handlerData = { args, @@ -149,7 +147,7 @@ function instrumentFetch(): void { }); // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - return originalFetch.apply(global, args).then( + return originalFetch.apply(WINDOW, args).then( (response: Response) => { triggerHandlers('fetch', { ...handlerData, @@ -190,7 +188,7 @@ interface SentryWrappedXMLHttpRequest extends XMLHttpRequest { /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /** Extract `method` from fetch call arguments */ function getFetchMethod(fetchArgs: any[] = []): string { - if ('Request' in global && isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) { + if ('Request' in WINDOW && isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) { return String(fetchArgs[0].method).toUpperCase(); } if (fetchArgs[1] && fetchArgs[1].method) { @@ -204,7 +202,7 @@ function getFetchUrl(fetchArgs: any[] = []): string { if (typeof fetchArgs[0] === 'string') { return fetchArgs[0]; } - if ('Request' in global && isInstanceOf(fetchArgs[0], Request)) { + if ('Request' in WINDOW && isInstanceOf(fetchArgs[0], Request)) { return fetchArgs[0].url; } return String(fetchArgs[0]); @@ -213,7 +211,7 @@ function getFetchUrl(fetchArgs: any[] = []): string { /** JSDoc */ function instrumentXHR(): void { - if (!('XMLHttpRequest' in global)) { + if (!('XMLHttpRequest' in WINDOW)) { return; } @@ -295,9 +293,9 @@ function instrumentHistory(): void { return; } - const oldOnPopState = global.onpopstate; - global.onpopstate = function (this: WindowEventHandlers, ...args: any[]): any { - const to = global.location.href; + const oldOnPopState = WINDOW.onpopstate; + WINDOW.onpopstate = function (this: WindowEventHandlers, ...args: any[]): any { + const to = WINDOW.location.href; // keep track of the current URL state, as we always receive only the updated state const from = lastHref; lastHref = to; @@ -336,8 +334,8 @@ function instrumentHistory(): void { }; } - fill(global.history, 'pushState', historyReplacementFunction); - fill(global.history, 'replaceState', historyReplacementFunction); + fill(WINDOW.history, 'pushState', historyReplacementFunction); + fill(WINDOW.history, 'replaceState', historyReplacementFunction); } const debounceDuration = 1000; @@ -452,7 +450,7 @@ function makeDOMEventHandler(handler: Function, globalListener: boolean = false) // Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together. clearTimeout(debounceTimerID); - debounceTimerID = global.setTimeout(() => { + debounceTimerID = WINDOW.setTimeout(() => { debounceTimerID = undefined; }, debounceDuration); }; @@ -481,7 +479,7 @@ type InstrumentedElement = Element & { /** JSDoc */ function instrumentDOM(): void { - if (!('document' in global)) { + if (!('document' in WINDOW)) { return; } @@ -490,8 +488,8 @@ function instrumentDOM(): void { // we instrument `addEventListener` so that we don't end up attaching this handler twice. const triggerDOMHandler = triggerHandlers.bind(null, 'dom'); const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true); - global.document.addEventListener('click', globalDOMEventHandler, false); - global.document.addEventListener('keypress', globalDOMEventHandler, false); + WINDOW.document.addEventListener('click', globalDOMEventHandler, false); + WINDOW.document.addEventListener('keypress', globalDOMEventHandler, false); // After hooking into click and keypress events bubbled up to `document`, we also hook into user-handled // clicks & keypresses, by adding an event listener of our own to any element to which they add a listener. That @@ -500,7 +498,7 @@ function instrumentDOM(): void { // guaranteed to fire at least once.) ['EventTarget', 'Node'].forEach((target: string) => { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access - const proto = (global as any)[target] && (global as any)[target].prototype; + const proto = (WINDOW as any)[target] && (WINDOW as any)[target].prototype; // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) { return; @@ -582,9 +580,9 @@ function instrumentDOM(): void { let _oldOnErrorHandler: OnErrorEventHandler = null; /** JSDoc */ function instrumentError(): void { - _oldOnErrorHandler = global.onerror; + _oldOnErrorHandler = WINDOW.onerror; - global.onerror = function (msg: any, url: any, line: any, column: any, error: any): boolean { + WINDOW.onerror = function (msg: any, url: any, line: any, column: any, error: any): boolean { triggerHandlers('error', { column, error, @@ -605,9 +603,9 @@ function instrumentError(): void { let _oldOnUnhandledRejectionHandler: ((e: any) => void) | null = null; /** JSDoc */ function instrumentUnhandledRejection(): void { - _oldOnUnhandledRejectionHandler = global.onunhandledrejection; + _oldOnUnhandledRejectionHandler = WINDOW.onunhandledrejection; - global.onunhandledrejection = function (e: any): boolean { + WINDOW.onunhandledrejection = function (e: any): boolean { triggerHandlers('unhandledrejection', e); if (_oldOnUnhandledRejectionHandler) { diff --git a/packages/utils/src/logger.ts b/packages/utils/src/logger.ts index b7111a216871..031c969a930d 100644 --- a/packages/utils/src/logger.ts +++ b/packages/utils/src/logger.ts @@ -1,9 +1,6 @@ import { WrappedFunction } from '@sentry/types'; -import { getGlobalObject, getGlobalSingleton } from './global'; - -// TODO: Implement different loggers for different environments -const global = getGlobalObject(); +import { getGlobalSingleton, GLOBAL_OBJ } from './global'; /** Prefix for logging strings */ const PREFIX = 'Sentry Logger '; @@ -27,13 +24,11 @@ interface Logger extends LoggerConsoleMethods { * @returns The results of the callback */ export function consoleSandbox(callback: () => T): T { - const global = getGlobalObject(); - - if (!('console' in global)) { + if (!('console' in GLOBAL_OBJ)) { return callback(); } - const originalConsole = global.console as Console & Record; + const originalConsole = GLOBAL_OBJ.console as Console & Record; const wrappedLevels: Partial = {}; // Restore all wrapped console methods @@ -41,7 +36,7 @@ export function consoleSandbox(callback: () => T): T { // TODO(v7): Remove this check as it's only needed for Node 6 const originalWrappedFunc = originalConsole[level] && (originalConsole[level] as WrappedFunction).__sentry_original__; - if (level in global.console && originalWrappedFunc) { + if (level in originalConsole && originalWrappedFunc) { wrappedLevels[level] = originalConsole[level] as LoggerConsoleMethods[typeof level]; originalConsole[level] = originalWrappedFunc as Console[typeof level]; } @@ -74,7 +69,7 @@ function makeLogger(): Logger { logger[name] = (...args: any[]) => { if (enabled) { consoleSandbox(() => { - global.console[name](`${PREFIX}[${name}]:`, ...args); + GLOBAL_OBJ.console[name](`${PREFIX}[${name}]:`, ...args); }); } }; diff --git a/packages/utils/src/misc.ts b/packages/utils/src/misc.ts index 964bc12c13bc..8c24439a47d4 100644 --- a/packages/utils/src/misc.ts +++ b/packages/utils/src/misc.ts @@ -1,20 +1,19 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Event, Exception, Mechanism, StackFrame } from '@sentry/types'; -import { getGlobalObject } from './global'; +import { GLOBAL_OBJ } from './global'; import { addNonEnumerableProperty } from './object'; import { snipLine } from './string'; -/** - * Extended Window interface that allows for Crypto API usage in IE browsers - */ -interface MsCryptoWindow extends Window { - msCrypto?: Crypto; +interface CryptoInternal { + getRandomValues(array: Uint8Array): Uint8Array; + randomUUID?(): string; } -/** Many browser now support native uuid v4 generation */ -interface CryptoWithRandomUUID extends Crypto { - randomUUID?(): string; +/** An interface for common properties on global */ +interface CryptoGlobal { + msCrypto?: CryptoInternal; + crypto?: CryptoInternal; } /** @@ -23,8 +22,8 @@ interface CryptoWithRandomUUID extends Crypto { * @returns string Generated UUID4. */ export function uuid4(): string { - const global = getGlobalObject() as MsCryptoWindow; - const crypto = (global.crypto || global.msCrypto) as CryptoWithRandomUUID; + const gbl = GLOBAL_OBJ as typeof GLOBAL_OBJ & CryptoGlobal; + const crypto = gbl.crypto || gbl.msCrypto; if (crypto && crypto.randomUUID) { return crypto.randomUUID().replace(/-/g, ''); diff --git a/packages/utils/src/supports.ts b/packages/utils/src/supports.ts index 4e3e10834bd2..c370bbb30621 100644 --- a/packages/utils/src/supports.ts +++ b/packages/utils/src/supports.ts @@ -1,4 +1,4 @@ -import { getGlobalObject } from './global'; +import { WINDOW } from './browser'; import { logger } from './logger'; /** @@ -56,7 +56,7 @@ export function supportsDOMException(): boolean { * @returns Answer to the given question. */ export function supportsFetch(): boolean { - if (!('fetch' in getGlobalObject())) { + if (!('fetch' in WINDOW)) { return false; } @@ -88,18 +88,16 @@ export function supportsNativeFetch(): boolean { return false; } - const global = getGlobalObject(); - // Fast path to avoid DOM I/O // eslint-disable-next-line @typescript-eslint/unbound-method - if (isNativeFetch(global.fetch)) { + if (isNativeFetch(WINDOW.fetch)) { return true; } // window.fetch is implemented, but is polyfilled or already wrapped (e.g: by a chrome extension) // so create a "pure" iframe to see if that has native fetch let result = false; - const doc = global.document; + const doc = WINDOW.document; // eslint-disable-next-line deprecation/deprecation if (doc && typeof (doc.createElement as unknown) === 'function') { try { @@ -127,7 +125,7 @@ export function supportsNativeFetch(): boolean { * @returns Answer to the given question. */ export function supportsReportingObserver(): boolean { - return 'ReportingObserver' in getGlobalObject(); + return 'ReportingObserver' in WINDOW; } /** @@ -166,13 +164,12 @@ export function supportsHistory(): boolean { // NOTE: in Chrome App environment, touching history.pushState, *even inside // a try/catch block*, will cause Chrome to output an error to console.error // borrowed from: https://github.com/angular/angular.js/pull/13945/files - const global = getGlobalObject(); /* eslint-disable @typescript-eslint/no-unsafe-member-access */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - const chrome = (global as any).chrome; + const chrome = (WINDOW as any).chrome; const isChromePackagedApp = chrome && chrome.app && chrome.app.runtime; /* eslint-enable @typescript-eslint/no-unsafe-member-access */ - const hasHistoryApi = 'history' in global && !!global.history.pushState && !!global.history.replaceState; + const hasHistoryApi = 'history' in WINDOW && !!WINDOW.history.pushState && !!WINDOW.history.replaceState; return !isChromePackagedApp && hasHistoryApi; } diff --git a/packages/utils/src/time.ts b/packages/utils/src/time.ts index 135bd2b5b5f4..b2d9628db5d9 100644 --- a/packages/utils/src/time.ts +++ b/packages/utils/src/time.ts @@ -1,4 +1,4 @@ -import { getGlobalObject } from './global'; +import { WINDOW } from './browser'; import { dynamicRequire, isNodeEnv } from './node'; /** @@ -41,7 +41,7 @@ interface Performance { * Wrapping the native API works around differences in behavior from different browsers. */ function getBrowserPerformance(): Performance | undefined { - const { performance } = getGlobalObject(); + const { performance } = WINDOW; if (!performance || !performance.now) { return undefined; } @@ -140,7 +140,7 @@ export const browserPerformanceTimeOrigin = ((): number | undefined => { // performance.timing.navigationStart, which results in poor results in performance data. We only treat time origin // data as reliable if they are within a reasonable threshold of the current time. - const { performance } = getGlobalObject(); + const { performance } = WINDOW; if (!performance || !performance.now) { _browserPerformanceTimeOriginMode = 'none'; return undefined; diff --git a/packages/utils/test/global.test.ts b/packages/utils/test/global.test.ts index 908cc59a9772..722abf678bb0 100644 --- a/packages/utils/test/global.test.ts +++ b/packages/utils/test/global.test.ts @@ -1,11 +1,11 @@ -import { getGlobalObject } from '../src/global'; +import { GLOBAL_OBJ } from '../src/global'; -describe('getGlobalObject()', () => { +describe('GLOBAL_OBJ', () => { test('should return the same object', () => { const backup = global.process; delete global.process; - const first = getGlobalObject(); - const second = getGlobalObject(); + const first = GLOBAL_OBJ; + const second = GLOBAL_OBJ; expect(first).toEqual(second); global.process = backup; });