Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ref(utils): Remove getGlobalObject() usage from @sentry/utils #5831

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions packages/utils/src/browser.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -115,9 +120,8 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string {
* A safe form of location.href
*/
export function getLocationHref(): string {
const global = getGlobalObject<Window>();
try {
return global.document.location.href;
return WINDOW.document.location.href;
} catch (oO) {
return '';
}
Expand All @@ -141,9 +145,8 @@ export function getLocationHref(): string {
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function getDomElement<E = any>(selector: string): E | null {
const global = getGlobalObject<Window>();
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;
}
28 changes: 18 additions & 10 deletions packages/utils/src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[];
};
Expand All @@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably type this stricter, but we can do that after we are done with all of the changes.

};
};
}

// 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:
//
Expand Down Expand Up @@ -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)) ||
Expand All @@ -69,8 +77,8 @@ const GLOBAL =
*
* @returns Global scope object
*/
export function getGlobalObject<T>(): T & SentryGlobal {
return GLOBAL as T & SentryGlobal;
export function getGlobalObject<T>(): T & InternalGlobal {
return GLOBAL_OBJ as T & InternalGlobal;
}

/**
Expand All @@ -81,12 +89,12 @@ export function getGlobalObject<T>(): 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<T>(name: keyof SentryGlobal['__SENTRY__'], creator: () => T, obj?: unknown): T {
const global = (obj || GLOBAL) as SentryGlobal;
const __SENTRY__ = (global.__SENTRY__ = global.__SENTRY__ || {});
export function getGlobalSingleton<T>(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;
}
50 changes: 24 additions & 26 deletions packages/utils/src/instrument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Window>();

export type InstrumentHandlerType =
| 'console'
| 'dom'
Expand Down Expand Up @@ -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);
}
};
});
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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) {
Expand All @@ -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]);
Expand All @@ -213,7 +211,7 @@ function getFetchUrl(fetchArgs: any[] = []): string {

/** JSDoc */
function instrumentXHR(): void {
if (!('XMLHttpRequest' in global)) {
if (!('XMLHttpRequest' in WINDOW)) {
return;
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
};
Expand Down Expand Up @@ -481,7 +479,7 @@ type InstrumentedElement = Element & {

/** JSDoc */
function instrumentDOM(): void {
if (!('document' in global)) {
if (!('document' in WINDOW)) {
return;
}

Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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,
Expand All @@ -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) {
Expand Down
15 changes: 5 additions & 10 deletions packages/utils/src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import { WrappedFunction } from '@sentry/types';

import { getGlobalObject, getGlobalSingleton } from './global';

// TODO: Implement different loggers for different environments
const global = getGlobalObject<Window | NodeJS.Global>();
import { getGlobalSingleton, GLOBAL_OBJ } from './global';

/** Prefix for logging strings */
const PREFIX = 'Sentry Logger ';
Expand All @@ -27,21 +24,19 @@ interface Logger extends LoggerConsoleMethods {
* @returns The results of the callback
*/
export function consoleSandbox<T>(callback: () => T): T {
const global = getGlobalObject<Window>();

if (!('console' in global)) {
if (!('console' in GLOBAL_OBJ)) {
return callback();
}

const originalConsole = global.console as Console & Record<string, unknown>;
const originalConsole = GLOBAL_OBJ.console as Console & Record<string, unknown>;
const wrappedLevels: Partial<LoggerConsoleMethods> = {};

// Restore all wrapped console methods
CONSOLE_LEVELS.forEach(level => {
// 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];
}
Expand Down Expand Up @@ -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);
});
}
};
Expand Down
21 changes: 10 additions & 11 deletions packages/utils/src/misc.ts
Original file line number Diff line number Diff line change
@@ -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;
}

/**
Expand All @@ -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, '');
Expand Down
Loading