Skip to content
Open
Show file tree
Hide file tree
Changes from 9 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
4 changes: 2 additions & 2 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ module.exports = [
import: createImport('init'),
ignore: ['next/router', 'next/constants'],
gzip: true,
limit: '46.5 KB',
limit: '47 KB',
},
// SvelteKit SDK (ESM)
{
Expand All @@ -222,7 +222,7 @@ module.exports = [
import: createImport('init'),
ignore: ['$app/stores'],
gzip: true,
limit: '42 KB',
limit: '43 KB',
},
// Node-Core SDK (ESM)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
enhanceFetchErrorMessages: false,
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Based on possible TypeError exceptions from https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch

// Network error (e.g. ad-blocked, offline, page does not exist, ...)
window.networkError = () => {
fetch('http://sentry-test-external.io/does-not-exist');
};

window.networkErrorSubdomain = () => {
fetch('http://subdomain.sentry-test-external.io/does-not-exist');
};

window.networkErrorWithPort = () => {
fetch('http://sentry-test-external.io:3000/does-not-exist');
};

// Invalid header also produces TypeError
window.invalidHeaderName = () => {
fetch('http://sentry-test-external.io/invalid-header-name', { headers: { 'C ontent-Type': 'text/xml' } });
};

// Invalid header value also produces TypeError
window.invalidHeaderValue = () => {
fetch('http://sentry-test-external.io/invalid-header-value', { headers: ['Content-Type', 'text/html', 'extra'] });
};

// Invalid URL scheme
window.invalidUrlScheme = () => {
fetch('blub://sentry-test-external.io/invalid-scheme');
};

// URL includes credentials
window.credentialsInUrl = () => {
fetch('https://user:password@sentry-test-external.io/credentials-in-url');
};

// Invalid mode
window.invalidMode = () => {
fetch('https://sentry-test-external.io/invalid-mode', { mode: 'navigate' });
};

// Invalid request method
window.invalidMethod = () => {
fetch('http://sentry-test-external.io/invalid-method', { method: 'CONNECT' });
};

// No-cors mode with cors-required method
window.noCorsMethod = () => {
fetch('http://sentry-test-external.io/no-cors-method', { mode: 'no-cors', method: 'PUT' });
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../utils/fixtures';
import { envelopeRequestParser, waitForErrorRequest } from '../../../utils/helpers';

sentryTest(
'enhanceFetchErrorMessages: false: enhances error for Sentry while preserving original',
async ({ getLocalTestUrl, page, browserName }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const reqPromise = waitForErrorRequest(page);
const pageErrorPromise = new Promise<string>(resolve => {
page.on('pageerror', error => {
resolve(error.message);
});
});

await page.goto(url);
await page.evaluate('networkError()');

const [req, pageErrorMessage] = await Promise.all([reqPromise, pageErrorPromise]);
const eventData = envelopeRequestParser(req);
const originalErrorMap: Record<string, string> = {
chromium: 'Failed to fetch',
webkit: 'Load failed',
firefox: 'NetworkError when attempting to fetch resource.',
};

const originalError = originalErrorMap[browserName];

expect(pageErrorMessage).toContain(originalError);
expect(pageErrorMessage).not.toContain('sentry-test-external.io');

expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'TypeError',
value: originalError,
mechanism: {
handled: false,
type: 'auto.browser.global_handlers.onunhandledrejection',
},
});
},
);

sentryTest(
'enhanceFetchErrorMessages: false: enhances subdomain errors',
async ({ getLocalTestUrl, page, browserName }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const reqPromise = waitForErrorRequest(page);
const pageErrorPromise = new Promise<string>(resolve => page.on('pageerror', error => resolve(error.message)));

await page.goto(url);
await page.evaluate('networkErrorSubdomain()');

const [req, pageErrorMessage] = await Promise.all([reqPromise, pageErrorPromise]);
const eventData = envelopeRequestParser(req);

const originalErrorMap: Record<string, string> = {
chromium: 'Failed to fetch',
webkit: 'Load failed',
firefox: 'NetworkError when attempting to fetch resource.',
};

const originalError = originalErrorMap[browserName];

expect(pageErrorMessage).toContain(originalError);
expect(pageErrorMessage).not.toContain('subdomain.sentry-test-external.io');
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'TypeError',
value: originalError,
mechanism: {
handled: false,
type: 'auto.browser.global_handlers.onunhandledrejection',
},
});
},
);

sentryTest(
'enhanceFetchErrorMessages: false: includes port in hostname',
async ({ getLocalTestUrl, page, browserName }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const reqPromise = waitForErrorRequest(page);

const pageErrorPromise = new Promise<string>(resolve => page.on('pageerror', error => resolve(error.message)));

await page.goto(url);
await page.evaluate('networkErrorWithPort()');

const [req, pageErrorMessage] = await Promise.all([reqPromise, pageErrorPromise]);
const eventData = envelopeRequestParser(req);

const originalErrorMap: Record<string, string> = {
chromium: 'Failed to fetch',
webkit: 'Load failed',
firefox: 'NetworkError when attempting to fetch resource.',
};

const originalError = originalErrorMap[browserName];

expect(pageErrorMessage).toContain(originalError);
expect(pageErrorMessage).not.toContain('sentry-test-external.io:3000');
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'TypeError',
value: originalError,
mechanism: {
handled: false,
type: 'auto.browser.global_handlers.onunhandledrejection',
},
});
},
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import * as Sentry from '@sentry/browser';

window.Sentry = Sentry;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
enhanceFetchErrorMessages: 'report-only',
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Based on possible TypeError exceptions from https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch

// Network error (e.g. ad-blocked, offline, page does not exist, ...)
window.networkError = () => {
fetch('http://sentry-test-external.io/does-not-exist');
};

window.networkErrorSubdomain = () => {
fetch('http://subdomain.sentry-test-external.io/does-not-exist');
};

window.networkErrorWithPort = () => {
fetch('http://sentry-test-external.io:3000/does-not-exist');
};

// Invalid header also produces TypeError
window.invalidHeaderName = () => {
fetch('http://sentry-test-external.io/invalid-header-name', { headers: { 'C ontent-Type': 'text/xml' } });
};

// Invalid header value also produces TypeError
window.invalidHeaderValue = () => {
fetch('http://sentry-test-external.io/invalid-header-value', { headers: ['Content-Type', 'text/html', 'extra'] });
};

// Invalid URL scheme
window.invalidUrlScheme = () => {
fetch('blub://sentry-test-external.io/invalid-scheme');
};

// URL includes credentials
window.credentialsInUrl = () => {
fetch('https://user:password@sentry-test-external.io/credentials-in-url');
};

// Invalid mode
window.invalidMode = () => {
fetch('https://sentry-test-external.io/invalid-mode', { mode: 'navigate' });
};

// Invalid request method
window.invalidMethod = () => {
fetch('http://sentry-test-external.io/invalid-method', { method: 'CONNECT' });
};

// No-cors mode with cors-required method
window.noCorsMethod = () => {
fetch('http://sentry-test-external.io/no-cors-method', { mode: 'no-cors', method: 'PUT' });
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { expect } from '@playwright/test';
import { sentryTest } from '../../../utils/fixtures';
import { envelopeRequestParser, waitForErrorRequest } from '../../../utils/helpers';

sentryTest(
'enhanceFetchErrorMessages: report-only: enhances error for Sentry while preserving original',
async ({ getLocalTestUrl, page, browserName }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const reqPromise = waitForErrorRequest(page);
const pageErrorPromise = new Promise<string>(resolve => page.on('pageerror', error => resolve(error.message)));

await page.goto(url);
await page.evaluate('networkError()');

const [req, pageErrorMessage] = await Promise.all([reqPromise, pageErrorPromise]);
const eventData = envelopeRequestParser(req);
const originalErrorMap: Record<string, string> = {
chromium: 'Failed to fetch',
webkit: 'Load failed',
firefox: 'NetworkError when attempting to fetch resource.',
};

const enhancedErrorMap: Record<string, string> = {
chromium: 'Failed to fetch (sentry-test-external.io)',
webkit: 'Load failed (sentry-test-external.io)',
firefox: 'NetworkError when attempting to fetch resource. (sentry-test-external.io)',
};

const originalError = originalErrorMap[browserName];
const enhancedError = enhancedErrorMap[browserName];

expect(pageErrorMessage).toContain(originalError);
expect(pageErrorMessage).not.toContain('sentry-test-external.io');

// Verify Sentry received the enhanced message
// Note: In report-only mode, the original error message remains unchanged
// at the JavaScript level (for third-party package compatibility),
// but Sentry gets the enhanced version via __sentry_fetch_url_host__
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'TypeError',
value: enhancedError,
mechanism: {
handled: false,
type: 'auto.browser.global_handlers.onunhandledrejection',
},
});
},
);

sentryTest(
'enhanceFetchErrorMessages: report-only: enhances subdomain errors',
async ({ getLocalTestUrl, page, browserName }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const reqPromise = waitForErrorRequest(page);
const pageErrorPromise = new Promise<string>(resolve => page.on('pageerror', error => resolve(error.message)));

await page.goto(url);
await page.evaluate('networkErrorSubdomain()');

const [req, pageErrorMessage] = await Promise.all([reqPromise, pageErrorPromise]);
const eventData = envelopeRequestParser(req);

const originalErrorMap: Record<string, string> = {
chromium: 'Failed to fetch',
webkit: 'Load failed',
firefox: 'NetworkError when attempting to fetch resource.',
};

const enhancedErrorMap: Record<string, string> = {
chromium: 'Failed to fetch (subdomain.sentry-test-external.io)',
webkit: 'Load failed (subdomain.sentry-test-external.io)',
firefox: 'NetworkError when attempting to fetch resource. (subdomain.sentry-test-external.io)',
};

const originalError = originalErrorMap[browserName];
const enhancedError = enhancedErrorMap[browserName];

expect(pageErrorMessage).toContain(originalError);
expect(pageErrorMessage).not.toContain('subdomain.sentry-test-external.io');
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'TypeError',
value: enhancedError,
mechanism: {
handled: false,
type: 'auto.browser.global_handlers.onunhandledrejection',
},
});
},
);

sentryTest(
'enhanceFetchErrorMessages: report-only: includes port in hostname',
async ({ getLocalTestUrl, page, browserName }) => {
const url = await getLocalTestUrl({ testDir: __dirname });
const reqPromise = waitForErrorRequest(page);

const pageErrorPromise = new Promise<string>(resolve => page.on('pageerror', error => resolve(error.message)));

await page.goto(url);
await page.evaluate('networkErrorWithPort()');

const [req, pageErrorMessage] = await Promise.all([reqPromise, pageErrorPromise]);
const eventData = envelopeRequestParser(req);

const originalErrorMap: Record<string, string> = {
chromium: 'Failed to fetch',
webkit: 'Load failed',
firefox: 'NetworkError when attempting to fetch resource.',
};

const enhancedErrorMap: Record<string, string> = {
chromium: 'Failed to fetch (sentry-test-external.io:3000)',
webkit: 'Load failed (sentry-test-external.io:3000)',
firefox: 'NetworkError when attempting to fetch resource. (sentry-test-external.io:3000)',
};

const originalError = originalErrorMap[browserName];
const enhancedError = enhancedErrorMap[browserName];

expect(pageErrorMessage).toContain(originalError);
expect(pageErrorMessage).not.toContain('sentry-test-external.io:3000');
expect(eventData.exception?.values).toHaveLength(1);
expect(eventData.exception?.values?.[0]).toMatchObject({
type: 'TypeError',
value: enhancedError,
mechanism: {
handled: false,
type: 'auto.browser.global_handlers.onunhandledrejection',
},
});
},
);
Loading
Loading