Skip to content

ref(browser): Streamline browser init() checks #16340

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

Merged
merged 1 commit into from
May 20, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ sentryTest(
if (hasDebugLogs()) {
expect(errorLogs.length).toEqual(1);
expect(errorLogs[0]).toEqual(
'[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
'[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
);
} else {
expect(errorLogs.length).toEqual(0);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ sentryTest('should not initialize when inside a Chrome browser extension', async
if (hasDebugLogs()) {
expect(errorLogs.length).toEqual(1);
expect(errorLogs[0]).toEqual(
'[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
'[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
);
} else {
expect(errorLogs.length).toEqual(0);
Expand Down
124 changes: 63 additions & 61 deletions packages/browser/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ import {
getLocationHref,
inboundFiltersIntegration,
initAndBind,
logger,
stackParserFromStackParserOptions,
supportsFetch,
} from '@sentry/core';
import type { BrowserClientOptions, BrowserOptions } from './client';
import { BrowserClient } from './client';
Expand All @@ -24,6 +22,22 @@ import { linkedErrorsIntegration } from './integrations/linkederrors';
import { defaultStackParser } from './stack-parsers';
import { makeFetchTransport } from './transports/fetch';

type ExtensionProperties = {
chrome?: Runtime;
browser?: Runtime;
nw?: unknown;
};
type Runtime = {
runtime?: {
id?: string;
};
};

/**
* A magic string that build tooling can leverage in order to inject a release value into the SDK.
*/
declare const __SENTRY_RELEASE__: string | undefined;

/** Get the default integrations for the browser SDK. */
export function getDefaultIntegrations(_options: Options): Integration[] {
/**
Expand Down Expand Up @@ -79,49 +93,6 @@ function dropTopLevelUndefinedKeys<T extends object>(obj: T): Partial<T> {
return mutatetedObj;
}

type ExtensionProperties = {
chrome?: Runtime;
browser?: Runtime;
nw?: unknown;
};
type Runtime = {
runtime?: {
id?: string;
};
};

function shouldShowBrowserExtensionError(): boolean {
const windowWithMaybeExtension =
typeof WINDOW.window !== 'undefined' && (WINDOW as typeof WINDOW & ExtensionProperties);
if (!windowWithMaybeExtension) {
// No need to show the error if we're not in a browser window environment (e.g. service workers)
return false;
}

const extensionKey = windowWithMaybeExtension.chrome ? 'chrome' : 'browser';
const extensionObject = windowWithMaybeExtension[extensionKey];

const runtimeId = extensionObject?.runtime?.id;
const href = getLocationHref() || '';

const extensionProtocols = ['chrome-extension:', 'moz-extension:', 'ms-browser-extension:', 'safari-web-extension:'];

// Running the SDK in a dedicated extension page and calling Sentry.init is fine; no risk of data leakage
const isDedicatedExtensionPage =
!!runtimeId && WINDOW === WINDOW.top && extensionProtocols.some(protocol => href.startsWith(`${protocol}//`));

// Running the SDK in NW.js, which appears like a browser extension but isn't, is also fine
// see: https://github.com/getsentry/sentry-javascript/issues/12668
const isNWjs = typeof windowWithMaybeExtension.nw !== 'undefined';

return !!runtimeId && !isDedicatedExtensionPage && !isNWjs;
}

/**
* A magic string that build tooling can leverage in order to inject a release value into the SDK.
*/
declare const __SENTRY_RELEASE__: string | undefined;

/**
* The Sentry Browser SDK Client.
*
Expand Down Expand Up @@ -169,25 +140,11 @@ declare const __SENTRY_RELEASE__: string | undefined;
* @see {@link BrowserOptions} for documentation on configuration options.
*/
export function init(browserOptions: BrowserOptions = {}): Client | undefined {
const options = applyDefaultOptions(browserOptions);

if (!options.skipBrowserExtensionCheck && shouldShowBrowserExtensionError()) {
if (DEBUG_BUILD) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.error(
'[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
);
});
}
if (!browserOptions.skipBrowserExtensionCheck && _checkForBrowserExtension()) {
return;
}

if (DEBUG_BUILD && !supportsFetch()) {
logger.warn(
'No Fetch API detected. The Sentry SDK requires a Fetch API compatible environment to send events. Please add a Fetch API polyfill.',
);
}
const options = applyDefaultOptions(browserOptions);
const clientOptions: BrowserClientOptions = {
...options,
stackParser: stackParserFromStackParserOptions(options.stackParser || defaultStackParser),
Expand All @@ -213,3 +170,48 @@ export function forceLoad(): void {
export function onLoad(callback: () => void): void {
callback();
}

function _isEmbeddedBrowserExtension(): boolean {
if (typeof WINDOW.window === 'undefined') {
// No need to show the error if we're not in a browser window environment (e.g. service workers)
return false;
}

const _window = WINDOW as typeof WINDOW & ExtensionProperties;

// Running the SDK in NW.js, which appears like a browser extension but isn't, is also fine
// see: https://github.com/getsentry/sentry-javascript/issues/12668
if (_window.nw) {
return false;
}

const extensionObject = _window['chrome'] || _window['browser'];

if (!extensionObject?.runtime?.id) {
return false;
}

const href = getLocationHref();
const extensionProtocols = ['chrome-extension', 'moz-extension', 'ms-browser-extension', 'safari-web-extension'];

// Running the SDK in a dedicated extension page and calling Sentry.init is fine; no risk of data leakage
const isDedicatedExtensionPage =
WINDOW === WINDOW.top && extensionProtocols.some(protocol => href.startsWith(`${protocol}://`));

return !isDedicatedExtensionPage;
}

function _checkForBrowserExtension(): true | void {
if (_isEmbeddedBrowserExtension()) {
if (DEBUG_BUILD) {
consoleSandbox(() => {
// eslint-disable-next-line no-console
console.error(
'[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
);
});
}

return true;
}
}
4 changes: 2 additions & 2 deletions packages/browser/test/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ describe('init', () => {

expect(consoleErrorSpy).toBeCalledTimes(1);
expect(consoleErrorSpy).toHaveBeenCalledWith(
'[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
'[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
);

consoleErrorSpy.mockRestore();
Expand All @@ -164,7 +164,7 @@ describe('init', () => {

expect(consoleErrorSpy).toBeCalledTimes(1);
expect(consoleErrorSpy).toHaveBeenCalledWith(
'[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
'[Sentry] You cannot use Sentry.init() in a browser extension, see: https://docs.sentry.io/platforms/javascript/best-practices/browser-extensions/',
);

consoleErrorSpy.mockRestore();
Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,9 +203,11 @@ export {
supportsDOMError,
supportsDOMException,
supportsErrorEvent,
// eslint-disable-next-line deprecation/deprecation
supportsFetch,
supportsHistory,
supportsNativeFetch,
// eslint-disable-next-line deprecation/deprecation
supportsReferrerPolicy,
supportsReportingObserver,
} from './utils-hoist/supports';
Expand Down
10 changes: 7 additions & 3 deletions packages/core/src/utils-hoist/supports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,11 @@ export function supportsHistory(): boolean {
* {@link supportsFetch}.
*
* @returns Answer to the given question.
* @deprecated This is no longer used and will be removed in a future major version.
*/
export function supportsFetch(): boolean {
export const supportsFetch = _isFetchSupported;

function _isFetchSupported(): boolean {
if (!('fetch' in WINDOW)) {
return false;
}
Expand Down Expand Up @@ -104,7 +107,7 @@ export function supportsNativeFetch(): boolean {
return true;
}

if (!supportsFetch()) {
if (!_isFetchSupported()) {
return false;
}

Expand Down Expand Up @@ -153,14 +156,15 @@ export function supportsReportingObserver(): boolean {
* {@link supportsReferrerPolicy}.
*
* @returns Answer to the given question.
* @deprecated This is no longer used and will be removed in a future major version.
*/
export function supportsReferrerPolicy(): boolean {
// Despite all stars in the sky saying that Edge supports old draft syntax, aka 'never', 'always', 'origin' and 'default'
// (see https://caniuse.com/#feat=referrer-policy),
// it doesn't. And it throws an exception instead of ignoring this parameter...
// REF: https://github.com/getsentry/raven-js/issues/1233

if (!supportsFetch()) {
if (!_isFetchSupported()) {
return false;
}

Expand Down
Loading