Skip to content

Commit 2a95451

Browse files
authored
Remove getGlobalObject usage from utils (#5831)
- Replaces `getGlobalObject` with a const `GLOBAL_OBJ` - Improvement over before since we only evaluate the detection code once - No need to lazily evaluate since `GLOBAL_OBJ` is always accessed - Also includes a `WINDOW` const which returns `GLOBAL_OBJ` but with `typeof GLOBAL_OBJ & Window` type. - This will eventually move to `@sentry/browser` to ensure stricter typing around the global object
1 parent 9056516 commit 2a95451

File tree

8 files changed

+80
-80
lines changed

8 files changed

+80
-80
lines changed

Diff for: packages/utils/src/browser.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1-
import { getGlobalObject } from './global';
1+
import { GLOBAL_OBJ } from './global';
22
import { isString } from './is';
33

4+
/**
5+
* TODO: Move me to @sentry/browser when @sentry/utils no longer contains any browser code
6+
*/
7+
export const WINDOW = GLOBAL_OBJ as typeof GLOBAL_OBJ & Window;
8+
49
/**
510
* Given a child DOM element, returns a query-selector statement describing that
611
* and its ancestors
@@ -115,9 +120,8 @@ function _htmlElementAsString(el: unknown, keyAttrs?: string[]): string {
115120
* A safe form of location.href
116121
*/
117122
export function getLocationHref(): string {
118-
const global = getGlobalObject<Window>();
119123
try {
120-
return global.document.location.href;
124+
return WINDOW.document.location.href;
121125
} catch (oO) {
122126
return '';
123127
}
@@ -141,9 +145,8 @@ export function getLocationHref(): string {
141145
*/
142146
// eslint-disable-next-line @typescript-eslint/no-explicit-any
143147
export function getDomElement<E = any>(selector: string): E | null {
144-
const global = getGlobalObject<Window>();
145-
if (global.document && global.document.querySelector) {
146-
return global.document.querySelector(selector) as unknown as E;
148+
if (WINDOW.document && WINDOW.document.querySelector) {
149+
return WINDOW.document.querySelector(selector) as unknown as E;
147150
}
148151
return null;
149152
}

Diff for: packages/utils/src/global.ts

+18-10
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
import { Integration } from '@sentry/types';
99

10-
/** Internal */
11-
interface SentryGlobal {
10+
/** Internal global with common properties and Sentry extensions */
11+
export interface InternalGlobal {
12+
navigator?: { userAgent?: string };
13+
console: Console;
1214
Sentry?: {
1315
Integrations?: Integration[];
1416
};
@@ -21,10 +23,15 @@ interface SentryGlobal {
2123
globalEventProcessors: any;
2224
hub: any;
2325
logger: any;
26+
extensions?: {
27+
/** Extension methods for the hub, which are bound to the current Hub instance */
28+
// eslint-disable-next-line @typescript-eslint/ban-types
29+
[key: string]: Function;
30+
};
2431
};
2532
}
2633

27-
// The code below for 'isGlobalObj' and 'GLOBAL' was copied from core-js before modification
34+
// The code below for 'isGlobalObj' and 'GLOBAL_OBJ' was copied from core-js before modification
2835
// https://github.com/zloirock/core-js/blob/1b944df55282cdc99c90db5f49eb0b6eda2cc0a3/packages/core-js/internals/global.js
2936
// core-js has the following licence:
3037
//
@@ -53,7 +60,8 @@ function isGlobalObj(obj: { Math?: Math }): any | undefined {
5360
return obj && obj.Math == Math ? obj : undefined;
5461
}
5562

56-
const GLOBAL =
63+
/** Get's the global object for the current JavaScript runtime */
64+
export const GLOBAL_OBJ: InternalGlobal =
5765
(typeof globalThis == 'object' && isGlobalObj(globalThis)) ||
5866
// eslint-disable-next-line no-restricted-globals
5967
(typeof window == 'object' && isGlobalObj(window)) ||
@@ -69,8 +77,8 @@ const GLOBAL =
6977
*
7078
* @returns Global scope object
7179
*/
72-
export function getGlobalObject<T>(): T & SentryGlobal {
73-
return GLOBAL as T & SentryGlobal;
80+
export function getGlobalObject<T>(): T & InternalGlobal {
81+
return GLOBAL_OBJ as T & InternalGlobal;
7482
}
7583

7684
/**
@@ -81,12 +89,12 @@ export function getGlobalObject<T>(): T & SentryGlobal {
8189
*
8290
* @param name name of the global singleton on __SENTRY__
8391
* @param creator creator Factory function to create the singleton if it doesn't already exist on `__SENTRY__`
84-
* @param obj (Optional) The global object on which to look for `__SENTRY__`, if not `getGlobalObject`'s return value
92+
* @param obj (Optional) The global object on which to look for `__SENTRY__`, if not `GLOBAL_OBJ`'s return value
8593
* @returns the singleton
8694
*/
87-
export function getGlobalSingleton<T>(name: keyof SentryGlobal['__SENTRY__'], creator: () => T, obj?: unknown): T {
88-
const global = (obj || GLOBAL) as SentryGlobal;
89-
const __SENTRY__ = (global.__SENTRY__ = global.__SENTRY__ || {});
95+
export function getGlobalSingleton<T>(name: keyof InternalGlobal['__SENTRY__'], creator: () => T, obj?: unknown): T {
96+
const gbl = (obj || GLOBAL_OBJ) as InternalGlobal;
97+
const __SENTRY__ = (gbl.__SENTRY__ = gbl.__SENTRY__ || {});
9098
const singleton = __SENTRY__[name] || (__SENTRY__[name] = creator());
9199
return singleton;
92100
}

Diff for: packages/utils/src/instrument.ts

+24-26
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@
33
/* eslint-disable @typescript-eslint/ban-types */
44
import { WrappedFunction } from '@sentry/types';
55

6-
import { getGlobalObject } from './global';
6+
import { WINDOW } from './browser';
77
import { isInstanceOf, isString } from './is';
88
import { CONSOLE_LEVELS, logger } from './logger';
99
import { fill } from './object';
1010
import { getFunctionName } from './stacktrace';
1111
import { supportsHistory, supportsNativeFetch } from './supports';
1212

13-
const global = getGlobalObject<Window>();
14-
1513
export type InstrumentHandlerType =
1614
| 'console'
1715
| 'dom'
@@ -105,22 +103,22 @@ function triggerHandlers(type: InstrumentHandlerType, data: any): void {
105103

106104
/** JSDoc */
107105
function instrumentConsole(): void {
108-
if (!('console' in global)) {
106+
if (!('console' in WINDOW)) {
109107
return;
110108
}
111109

112110
CONSOLE_LEVELS.forEach(function (level: string): void {
113-
if (!(level in global.console)) {
111+
if (!(level in WINDOW.console)) {
114112
return;
115113
}
116114

117-
fill(global.console, level, function (originalConsoleMethod: () => any): Function {
115+
fill(WINDOW.console, level, function (originalConsoleMethod: () => any): Function {
118116
return function (...args: any[]): void {
119117
triggerHandlers('console', { args, level });
120118

121119
// this fails for some browsers. :(
122120
if (originalConsoleMethod) {
123-
originalConsoleMethod.apply(global.console, args);
121+
originalConsoleMethod.apply(WINDOW.console, args);
124122
}
125123
};
126124
});
@@ -133,7 +131,7 @@ function instrumentFetch(): void {
133131
return;
134132
}
135133

136-
fill(global, 'fetch', function (originalFetch: () => void): () => void {
134+
fill(WINDOW, 'fetch', function (originalFetch: () => void): () => void {
137135
return function (...args: any[]): void {
138136
const handlerData = {
139137
args,
@@ -149,7 +147,7 @@ function instrumentFetch(): void {
149147
});
150148

151149
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
152-
return originalFetch.apply(global, args).then(
150+
return originalFetch.apply(WINDOW, args).then(
153151
(response: Response) => {
154152
triggerHandlers('fetch', {
155153
...handlerData,
@@ -190,7 +188,7 @@ interface SentryWrappedXMLHttpRequest extends XMLHttpRequest {
190188
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
191189
/** Extract `method` from fetch call arguments */
192190
function getFetchMethod(fetchArgs: any[] = []): string {
193-
if ('Request' in global && isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) {
191+
if ('Request' in WINDOW && isInstanceOf(fetchArgs[0], Request) && fetchArgs[0].method) {
194192
return String(fetchArgs[0].method).toUpperCase();
195193
}
196194
if (fetchArgs[1] && fetchArgs[1].method) {
@@ -204,7 +202,7 @@ function getFetchUrl(fetchArgs: any[] = []): string {
204202
if (typeof fetchArgs[0] === 'string') {
205203
return fetchArgs[0];
206204
}
207-
if ('Request' in global && isInstanceOf(fetchArgs[0], Request)) {
205+
if ('Request' in WINDOW && isInstanceOf(fetchArgs[0], Request)) {
208206
return fetchArgs[0].url;
209207
}
210208
return String(fetchArgs[0]);
@@ -213,7 +211,7 @@ function getFetchUrl(fetchArgs: any[] = []): string {
213211

214212
/** JSDoc */
215213
function instrumentXHR(): void {
216-
if (!('XMLHttpRequest' in global)) {
214+
if (!('XMLHttpRequest' in WINDOW)) {
217215
return;
218216
}
219217

@@ -295,9 +293,9 @@ function instrumentHistory(): void {
295293
return;
296294
}
297295

298-
const oldOnPopState = global.onpopstate;
299-
global.onpopstate = function (this: WindowEventHandlers, ...args: any[]): any {
300-
const to = global.location.href;
296+
const oldOnPopState = WINDOW.onpopstate;
297+
WINDOW.onpopstate = function (this: WindowEventHandlers, ...args: any[]): any {
298+
const to = WINDOW.location.href;
301299
// keep track of the current URL state, as we always receive only the updated state
302300
const from = lastHref;
303301
lastHref = to;
@@ -336,8 +334,8 @@ function instrumentHistory(): void {
336334
};
337335
}
338336

339-
fill(global.history, 'pushState', historyReplacementFunction);
340-
fill(global.history, 'replaceState', historyReplacementFunction);
337+
fill(WINDOW.history, 'pushState', historyReplacementFunction);
338+
fill(WINDOW.history, 'replaceState', historyReplacementFunction);
341339
}
342340

343341
const debounceDuration = 1000;
@@ -452,7 +450,7 @@ function makeDOMEventHandler(handler: Function, globalListener: boolean = false)
452450

453451
// Start a new debounce timer that will prevent us from capturing multiple events that should be grouped together.
454452
clearTimeout(debounceTimerID);
455-
debounceTimerID = global.setTimeout(() => {
453+
debounceTimerID = WINDOW.setTimeout(() => {
456454
debounceTimerID = undefined;
457455
}, debounceDuration);
458456
};
@@ -481,7 +479,7 @@ type InstrumentedElement = Element & {
481479

482480
/** JSDoc */
483481
function instrumentDOM(): void {
484-
if (!('document' in global)) {
482+
if (!('document' in WINDOW)) {
485483
return;
486484
}
487485

@@ -490,8 +488,8 @@ function instrumentDOM(): void {
490488
// we instrument `addEventListener` so that we don't end up attaching this handler twice.
491489
const triggerDOMHandler = triggerHandlers.bind(null, 'dom');
492490
const globalDOMEventHandler = makeDOMEventHandler(triggerDOMHandler, true);
493-
global.document.addEventListener('click', globalDOMEventHandler, false);
494-
global.document.addEventListener('keypress', globalDOMEventHandler, false);
491+
WINDOW.document.addEventListener('click', globalDOMEventHandler, false);
492+
WINDOW.document.addEventListener('keypress', globalDOMEventHandler, false);
495493

496494
// After hooking into click and keypress events bubbled up to `document`, we also hook into user-handled
497495
// 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 {
500498
// guaranteed to fire at least once.)
501499
['EventTarget', 'Node'].forEach((target: string) => {
502500
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
503-
const proto = (global as any)[target] && (global as any)[target].prototype;
501+
const proto = (WINDOW as any)[target] && (WINDOW as any)[target].prototype;
504502
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, no-prototype-builtins
505503
if (!proto || !proto.hasOwnProperty || !proto.hasOwnProperty('addEventListener')) {
506504
return;
@@ -582,9 +580,9 @@ function instrumentDOM(): void {
582580
let _oldOnErrorHandler: OnErrorEventHandler = null;
583581
/** JSDoc */
584582
function instrumentError(): void {
585-
_oldOnErrorHandler = global.onerror;
583+
_oldOnErrorHandler = WINDOW.onerror;
586584

587-
global.onerror = function (msg: any, url: any, line: any, column: any, error: any): boolean {
585+
WINDOW.onerror = function (msg: any, url: any, line: any, column: any, error: any): boolean {
588586
triggerHandlers('error', {
589587
column,
590588
error,
@@ -605,9 +603,9 @@ function instrumentError(): void {
605603
let _oldOnUnhandledRejectionHandler: ((e: any) => void) | null = null;
606604
/** JSDoc */
607605
function instrumentUnhandledRejection(): void {
608-
_oldOnUnhandledRejectionHandler = global.onunhandledrejection;
606+
_oldOnUnhandledRejectionHandler = WINDOW.onunhandledrejection;
609607

610-
global.onunhandledrejection = function (e: any): boolean {
608+
WINDOW.onunhandledrejection = function (e: any): boolean {
611609
triggerHandlers('unhandledrejection', e);
612610

613611
if (_oldOnUnhandledRejectionHandler) {

Diff for: packages/utils/src/logger.ts

+5-10
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { WrappedFunction } from '@sentry/types';
22

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

85
/** Prefix for logging strings */
96
const PREFIX = 'Sentry Logger ';
@@ -27,21 +24,19 @@ interface Logger extends LoggerConsoleMethods {
2724
* @returns The results of the callback
2825
*/
2926
export function consoleSandbox<T>(callback: () => T): T {
30-
const global = getGlobalObject<Window>();
31-
32-
if (!('console' in global)) {
27+
if (!('console' in GLOBAL_OBJ)) {
3328
return callback();
3429
}
3530

36-
const originalConsole = global.console as Console & Record<string, unknown>;
31+
const originalConsole = GLOBAL_OBJ.console as Console & Record<string, unknown>;
3732
const wrappedLevels: Partial<LoggerConsoleMethods> = {};
3833

3934
// Restore all wrapped console methods
4035
CONSOLE_LEVELS.forEach(level => {
4136
// TODO(v7): Remove this check as it's only needed for Node 6
4237
const originalWrappedFunc =
4338
originalConsole[level] && (originalConsole[level] as WrappedFunction).__sentry_original__;
44-
if (level in global.console && originalWrappedFunc) {
39+
if (level in originalConsole && originalWrappedFunc) {
4540
wrappedLevels[level] = originalConsole[level] as LoggerConsoleMethods[typeof level];
4641
originalConsole[level] = originalWrappedFunc as Console[typeof level];
4742
}
@@ -74,7 +69,7 @@ function makeLogger(): Logger {
7469
logger[name] = (...args: any[]) => {
7570
if (enabled) {
7671
consoleSandbox(() => {
77-
global.console[name](`${PREFIX}[${name}]:`, ...args);
72+
GLOBAL_OBJ.console[name](`${PREFIX}[${name}]:`, ...args);
7873
});
7974
}
8075
};

Diff for: packages/utils/src/misc.ts

+10-11
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { Event, Exception, Mechanism, StackFrame } from '@sentry/types';
33

4-
import { getGlobalObject } from './global';
4+
import { GLOBAL_OBJ } from './global';
55
import { addNonEnumerableProperty } from './object';
66
import { snipLine } from './string';
77

8-
/**
9-
* Extended Window interface that allows for Crypto API usage in IE browsers
10-
*/
11-
interface MsCryptoWindow extends Window {
12-
msCrypto?: Crypto;
8+
interface CryptoInternal {
9+
getRandomValues(array: Uint8Array): Uint8Array;
10+
randomUUID?(): string;
1311
}
1412

15-
/** Many browser now support native uuid v4 generation */
16-
interface CryptoWithRandomUUID extends Crypto {
17-
randomUUID?(): string;
13+
/** An interface for common properties on global */
14+
interface CryptoGlobal {
15+
msCrypto?: CryptoInternal;
16+
crypto?: CryptoInternal;
1817
}
1918

2019
/**
@@ -23,8 +22,8 @@ interface CryptoWithRandomUUID extends Crypto {
2322
* @returns string Generated UUID4.
2423
*/
2524
export function uuid4(): string {
26-
const global = getGlobalObject() as MsCryptoWindow;
27-
const crypto = (global.crypto || global.msCrypto) as CryptoWithRandomUUID;
25+
const gbl = GLOBAL_OBJ as typeof GLOBAL_OBJ & CryptoGlobal;
26+
const crypto = gbl.crypto || gbl.msCrypto;
2827

2928
if (crypto && crypto.randomUUID) {
3029
return crypto.randomUUID().replace(/-/g, '');

0 commit comments

Comments
 (0)