Skip to content
Open
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
18 changes: 10 additions & 8 deletions packages/core/src/instrument/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,16 +136,18 @@ function instrumentFetch(onFetchResolved?: (response: Response) => void): void {
error.message === 'NetworkError when attempting to fetch resource.')
) {
try {
const url = new URL(handlerData.fetchData.url);
const url = new URL(handlerData.fetchData.url, GLOBAL_OBJ.location?.href);
const hostname = url.host;

if (enhanceOption === 'always') {
// Modify the error message directly
error.message = `${error.message} (${hostname})`;
} else {
// Store hostname as non-enumerable property for Sentry-only enhancement
// This preserves the original error message for third-party packages
addNonEnumerableProperty(error, '__sentry_fetch_url_host__', hostname);
if (hostname) {
if (enhanceOption === 'always') {
// Modify the error message directly
error.message = `${error.message} (${hostname})`;
} else {
// Store hostname as non-enumerable property for Sentry-only enhancement
// This preserves the original error message for third-party packages
addNonEnumerableProperty(error, '__sentry_fetch_url_host__', hostname);
}
}
} catch {
// ignore it if errors happen here
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/utils/worldwide.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { SdkSource } from './env';
/** Internal global with common properties and Sentry extensions */
export type InternalGlobal = {
navigator?: { userAgent?: string; maxTouchPoints?: number };
location?: { href?: string };
console: Console;
PerformanceObserver?: any;
Sentry?: any;
Expand Down
36 changes: 36 additions & 0 deletions packages/core/test/lib/instrument/fetch.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,40 @@ describe('instrument > addFetchInstrumentationHandler', () => {

expect(error.message).toBe('Failed to fetch (example.com)');
});

describe('relative URLs', () => {
const globalWithLocation = GLOBAL_OBJ as typeof GLOBAL_OBJ & { location?: { href: string } };

afterEach(() => {
Reflect.deleteProperty(globalWithLocation, 'location');
});

async function failWith(target: string): Promise<TypeError> {
const error = new TypeError('Failed to fetch');
globalThis.fetch = vi.fn<typeof fetch>().mockRejectedValue(error);
addFetchInstrumentationHandler(() => undefined);
await expect(globalThis.fetch(target)).rejects.toBe(error);
return error;
}

it.each([
['a relative path', 'https://app.example.com/some/page?x=1', '/api/users', 'Failed to fetch (app.example.com)'],
['an empty URL (`fetch("")`)', 'https://app.example.com/some/page?x=1', '', 'Failed to fetch (app.example.com)'],
['a relative path with no current page', undefined, '/api/users', 'Failed to fetch'],
['a relative path on a `file://` page', 'file:///C:/app/index.html', '/api/users', 'Failed to fetch'],
['a relative path with an opaque origin', 'null', '/api/users', 'Failed to fetch'],
[
'an absolute URL, ignoring the current page',
'https://app.example.com/some/page',
'https://other.example.org/x',
'Failed to fetch (other.example.org)',
],
])('handles %s', async (_name, href, target, expected) => {
if (href) {
globalWithLocation.location = { href };
}

expect((await failWith(target)).message).toBe(expected);
});
});
});