Skip to content

Commit

Permalink
fix(browser): Improve browser extension error message check (#12146)
Browse files Browse the repository at this point in the history
Make our check for when we abort and log an error due to
`Sentry.init` being used in a browser extension a bit more fine-gained.
In particular, we now do not abort the SDK initialization if we detect
that the SDK is running in a browser-extension dedicated window (e.g. a
URL starting with `chrome-extension://`).
  • Loading branch information
Lms24 committed May 27, 2024
1 parent 83c255a commit 8dca102
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 18 deletions.
40 changes: 25 additions & 15 deletions packages/browser/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,22 +60,32 @@ function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOptions {
return { ...defaultOptions, ...optionsArg };
}

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

function shouldShowBrowserExtensionError(): boolean {
const windowWithMaybeChrome = WINDOW as typeof WINDOW & { chrome?: { runtime?: { id?: string } } };
const isInsideChromeExtension =
windowWithMaybeChrome &&
windowWithMaybeChrome.chrome &&
windowWithMaybeChrome.chrome.runtime &&
windowWithMaybeChrome.chrome.runtime.id;

const windowWithMaybeBrowser = WINDOW as typeof WINDOW & { browser?: { runtime?: { id?: string } } };
const isInsideBrowserExtension =
windowWithMaybeBrowser &&
windowWithMaybeBrowser.browser &&
windowWithMaybeBrowser.browser.runtime &&
windowWithMaybeBrowser.browser.runtime.id;

return !!isInsideBrowserExtension || !!isInsideChromeExtension;
const windowWithMaybeExtension = WINDOW as typeof WINDOW & ExtensionProperties;

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

const runtimeId = extensionObject && extensionObject.runtime && extensionObject.runtime.id;
const href = (WINDOW.location && WINDOW.location.href) || '';

const extensionProtocols = ['chrome-extension:', 'moz-extension:', 'ms-browser-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}//`));

return !!runtimeId && !isDedicatedExtensionPage;
}

/**
Expand Down
31 changes: 28 additions & 3 deletions packages/browser/test/unit/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,14 +135,16 @@ describe('init', () => {
new MockIntegration('MockIntegration 0.2'),
];

const originalLocation = WINDOW.location || {};

const options = getDefaultBrowserOptions({ dsn: PUBLIC_DSN, defaultIntegrations: DEFAULT_INTEGRATIONS });

afterEach(() => {
Object.defineProperty(WINDOW, 'chrome', { value: undefined, writable: true });
Object.defineProperty(WINDOW, 'browser', { value: undefined, writable: true });
});

it('should log a browser extension error if executed inside a Chrome extension', () => {
it('logs a browser extension error if executed inside a Chrome extension', () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

Object.defineProperty(WINDOW, 'chrome', {
Expand All @@ -160,7 +162,7 @@ describe('init', () => {
consoleErrorSpy.mockRestore();
});

it('should log a browser extension error if executed inside a Firefox/Safari extension', () => {
it('logs a browser extension error if executed inside a Firefox/Safari extension', () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

Object.defineProperty(WINDOW, 'browser', { value: { runtime: { id: 'mock-extension-id' } }, writable: true });
Expand All @@ -175,7 +177,30 @@ describe('init', () => {
consoleErrorSpy.mockRestore();
});

it('should not log a browser extension error if executed inside regular browser environment', () => {
it.each(['chrome-extension', 'moz-extension', 'ms-browser-extension'])(
"doesn't log a browser extension error if executed inside an extension running in a dedicated page (%s)",
extensionProtocol => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

// @ts-expect-error - this is a hack to simulate a dedicated page in a browser extension
delete WINDOW.location;
// @ts-expect-error - this is a hack to simulate a dedicated page in a browser extension
WINDOW.location = {
href: `${extensionProtocol}://mock-extension-id/dedicated-page.html`,
};

Object.defineProperty(WINDOW, 'browser', { value: { runtime: { id: 'mock-extension-id' } }, writable: true });

init(options);

expect(consoleErrorSpy).toBeCalledTimes(0);

consoleErrorSpy.mockRestore();
WINDOW.location = originalLocation;
},
);

it("doesn't log a browser extension error if executed inside regular browser environment", () => {
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});

init(options);
Expand Down

0 comments on commit 8dca102

Please sign in to comment.