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

fix(browser): Improve browser extension error message check #12146

Merged
merged 2 commits into from
May 27, 2024
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
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';
Copy link
Member

Choose a reason for hiding this comment

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

to clarify, when would you have window.browser.xxx, and would that mean this is an extension??

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, we introduced this in the original browser extension check (https://github.com/getsentry/sentry-javascript/pull/10844/files)

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
Loading