Skip to content

Commit 1b85c6b

Browse files
committed
prevent initialization inside a browser extension
1 parent 8063b3e commit 1b85c6b

File tree

6 files changed

+164
-0
lines changed

6 files changed

+164
-0
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.browser = { runtime: { id: 'mock-extension-id' } };
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../utils/fixtures';
3+
4+
sentryTest('should not initialize when inside a Chrome browser extension', async ({ getLocalTestUrl, page }) => {
5+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
6+
return route.fulfill({
7+
status: 200,
8+
contentType: 'application/json',
9+
body: JSON.stringify({ id: 'test-id' }),
10+
});
11+
});
12+
13+
const errorLogs: string[] = [];
14+
15+
page.on('console', message => {
16+
if (message.type() === 'error') errorLogs.push(message.text());
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
await page.goto(url);
21+
22+
const isInitialized = await page.evaluate(() => {
23+
return !!(window as any).Sentry.isInitialized();
24+
});
25+
26+
expect(isInitialized).toEqual(false);
27+
expect(errorLogs.length).toEqual(1);
28+
expect(errorLogs[0]).toEqual(
29+
'[Sentry] You cannot run Sentry this way in an extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions',
30+
);
31+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Sentry from '@sentry/browser';
2+
3+
window.Sentry = Sentry;
4+
window.chrome = { runtime: { id: 'mock-extension-id' } };
5+
6+
Sentry.init({
7+
dsn: 'https://[email protected]/1337',
8+
});
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { expect } from '@playwright/test';
2+
import { sentryTest } from '../../../utils/fixtures';
3+
4+
sentryTest('should not initialize when inside a Chrome browser extension', async ({ getLocalTestUrl, page }) => {
5+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
6+
return route.fulfill({
7+
status: 200,
8+
contentType: 'application/json',
9+
body: JSON.stringify({ id: 'test-id' }),
10+
});
11+
});
12+
13+
const errorLogs: string[] = [];
14+
15+
page.on('console', message => {
16+
if (message.type() === 'error') errorLogs.push(message.text());
17+
});
18+
19+
const url = await getLocalTestUrl({ testDir: __dirname });
20+
await page.goto(url);
21+
22+
const isInitialized = await page.evaluate(() => {
23+
return !!(window as any).Sentry.isInitialized();
24+
});
25+
26+
expect(isInitialized).toEqual(false);
27+
expect(errorLogs.length).toEqual(1);
28+
expect(errorLogs[0]).toEqual(
29+
'[Sentry] You cannot run Sentry this way in an extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions',
30+
);
31+
});

packages/browser/src/sdk.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import type { DsnLike, Integration, Options, UserFeedback } from '@sentry/types';
1212
import {
1313
addHistoryInstrumentationHandler,
14+
consoleSandbox,
1415
logger,
1516
stackParserFromStackParserOptions,
1617
supportsFetch,
@@ -116,6 +117,31 @@ export function init(options: BrowserOptions = {}): void {
116117
options.sendClientReports = true;
117118
}
118119

120+
// inside Chrome extension (chrome.* API)
121+
const windowWithMaybeChrome = WINDOW as typeof WINDOW & { chrome?: { runtime?: { id?: string } } };
122+
123+
// inside Firefox/Safari extension (browser.* API)
124+
const windowWithMaybeBrowser = WINDOW as typeof WINDOW & { browser?: { runtime?: { id?: string } } };
125+
126+
if (
127+
(windowWithMaybeBrowser &&
128+
windowWithMaybeBrowser.browser &&
129+
windowWithMaybeBrowser.browser.runtime &&
130+
windowWithMaybeBrowser.browser.runtime.id) ||
131+
(windowWithMaybeChrome &&
132+
windowWithMaybeChrome.chrome &&
133+
windowWithMaybeChrome.chrome.runtime &&
134+
windowWithMaybeChrome.chrome.runtime.id)
135+
) {
136+
consoleSandbox(() => {
137+
// eslint-disable-next-line no-console
138+
console.error(
139+
'[Sentry] You cannot run Sentry this way in an extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions',
140+
);
141+
});
142+
return;
143+
}
144+
119145
if (DEBUG_BUILD) {
120146
if (!supportsFetch()) {
121147
logger.warn(

packages/browser/test/unit/sdk.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type { Client, Integration } from '@sentry/types';
44
import { resolvedSyncPromise } from '@sentry/utils';
55

66
import type { BrowserOptions } from '../../src';
7+
import { WINDOW } from '../../src';
78
import { init } from '../../src/sdk';
89

910
const PUBLIC_DSN = 'https://username@domain/123';
@@ -127,4 +128,63 @@ describe('init', () => {
127128
expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1);
128129
expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0);
129130
});
131+
132+
describe('initialization error in browser extension', () => {
133+
const DEFAULT_INTEGRATIONS: Integration[] = [
134+
new MockIntegration('MockIntegration 0.1'),
135+
new MockIntegration('MockIntegration 0.2'),
136+
];
137+
138+
const options = getDefaultBrowserOptions({ dsn: PUBLIC_DSN, defaultIntegrations: DEFAULT_INTEGRATIONS });
139+
140+
it('should not log a browser extension error if executed inside regular browser environment', () => {
141+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
142+
143+
init(options);
144+
145+
expect(consoleErrorSpy).toBeCalledTimes(0);
146+
147+
consoleErrorSpy.mockRestore();
148+
});
149+
150+
it('should log a browser extension error if executed inside a Chrome extension', () => {
151+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
152+
153+
Object.defineProperty(WINDOW, 'chrome', {
154+
value: { runtime: { id: 'mock-extension-id' } },
155+
writable: true,
156+
});
157+
158+
init(options);
159+
160+
expect(consoleErrorSpy).toBeCalledTimes(1);
161+
expect(consoleErrorSpy).toHaveBeenCalledWith(
162+
'[Sentry] You cannot run Sentry this way in an extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions',
163+
);
164+
165+
consoleErrorSpy.mockRestore();
166+
167+
Object.defineProperty(WINDOW, 'chrome', {
168+
value: null,
169+
writable: true,
170+
});
171+
});
172+
173+
it('should log a browser extension error if executed inside a Firefox/Safari extension', () => {
174+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
175+
176+
Object.defineProperty(WINDOW, 'browser', { value: { runtime: { id: 'mock-extension-id' } }, writable: true });
177+
178+
init(options);
179+
180+
expect(consoleErrorSpy).toBeCalledTimes(1);
181+
expect(consoleErrorSpy).toHaveBeenCalledWith(
182+
'[Sentry] You cannot run Sentry this way in an extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions',
183+
);
184+
185+
consoleErrorSpy.mockRestore();
186+
187+
Object.defineProperty(WINDOW, 'browser', { value: null, writable: true });
188+
});
189+
});
130190
});

0 commit comments

Comments
 (0)