Skip to content

Commit 3c15719

Browse files
committed
feat(core): Add parseStringToURL method
1 parent 3d63621 commit 3c15719

File tree

3 files changed

+51
-17
lines changed

3 files changed

+51
-17
lines changed

packages/core/src/fetch.ts

+6-16
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { SentryNonRecordingSpan } from './tracing/sentryNonRecordingSpan';
55
import type { FetchBreadcrumbHint, HandlerDataFetch, Span, SpanOrigin } from './types-hoist';
66
import { SENTRY_BAGGAGE_KEY_PREFIX } from './utils-hoist/baggage';
77
import { isInstanceOf } from './utils-hoist/is';
8-
import { parseUrl, stripUrlQueryAndFragment } from './utils-hoist/url';
8+
import { parseStringToURL, stripUrlQueryAndFragment } from './utils-hoist/url';
99
import { hasSpansEnabled } from './utils/hasSpansEnabled';
1010
import { getActiveSpan } from './utils/spanUtils';
1111
import { getTraceData } from './utils/traceData';
@@ -53,8 +53,7 @@ export function instrumentFetchRequest(
5353
return undefined;
5454
}
5555

56-
const fullUrl = getFullURL(url);
57-
const parsedUrl = fullUrl ? parseUrl(fullUrl) : parseUrl(url);
56+
const parsedUrl = parseStringToURL(url);
5857

5958
const hasParent = !!getActiveSpan();
6059

@@ -66,12 +65,12 @@ export function instrumentFetchRequest(
6665
url,
6766
type: 'fetch',
6867
'http.method': method,
69-
'http.url': fullUrl,
70-
'server.address': parsedUrl?.host,
68+
'http.url': parsedUrl?.href || url,
7169
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: spanOrigin,
7270
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'http.client',
73-
...(parsedUrl?.search && { 'http.query': parsedUrl?.search }),
74-
...(parsedUrl?.hash && { 'http.fragment': parsedUrl?.hash }),
71+
...(parsedUrl?.host && { 'server.address': parsedUrl.host }),
72+
...(parsedUrl?.search && { 'http.query': parsedUrl.search }),
73+
...(parsedUrl?.hash && { 'http.fragment': parsedUrl.hash }),
7574
},
7675
})
7776
: new SentryNonRecordingSpan();
@@ -215,15 +214,6 @@ function _addTracingHeadersToFetchRequest(
215214
}
216215
}
217216

218-
function getFullURL(url: string): string | undefined {
219-
try {
220-
const parsed = new URL(url);
221-
return parsed.href;
222-
} catch {
223-
return undefined;
224-
}
225-
}
226-
227217
function endSpan(span: Span, handlerData: HandlerDataFetch): void {
228218
if (handlerData.response) {
229219
setHttpStatus(span, handlerData.response.status);

packages/core/src/utils-hoist/url.ts

+27
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,33 @@ type PartialURL = {
77
hash?: string;
88
};
99

10+
interface URLwithCanParse extends URL {
11+
canParse: (url: string) => boolean;
12+
}
13+
14+
/**
15+
* Parses string to a URL object
16+
*
17+
* @param url - The URL to parse
18+
* @returns The parsed URL object or undefined if the URL is invalid
19+
*/
20+
export function parseStringToURL(url: string): URL | undefined {
21+
try {
22+
// Node 20+, Chrome 120+, Firefox 115+, Safari 17+
23+
if ('canParse' in URL) {
24+
// Use `canParse` to short-circuit the URL constructor if it's not a valid URL
25+
// This is faster than trying to construct the URL and catching the error
26+
return (URL as unknown as URLwithCanParse).canParse(url) ? new URL(url) : undefined;
27+
} else {
28+
return new URL(url);
29+
}
30+
} catch {
31+
// empty body
32+
}
33+
34+
return undefined;
35+
}
36+
1037
/**
1138
* Parses string form of URL into an object
1239
* // borrowed from https://tools.ietf.org/html/rfc3986#appendix-B

packages/core/test/utils-hoist/url.test.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest';
2-
import { getSanitizedUrlString, parseUrl, stripUrlQueryAndFragment } from '../../src/utils-hoist/url';
2+
import { getSanitizedUrlString, parseStringToURL, parseUrl, stripUrlQueryAndFragment } from '../../src/utils-hoist/url';
33

44
describe('stripQueryStringAndFragment', () => {
55
const urlString = 'http://dogs.are.great:1231/yay/';
@@ -196,3 +196,20 @@ describe('parseUrl', () => {
196196
expect(parseUrl(url)).toEqual(expected);
197197
});
198198
});
199+
200+
describe('parseStringToURL', () => {
201+
it('returns undefined for invalid URLs', () => {
202+
expect(parseStringToURL('invalid-url')).toBeUndefined();
203+
});
204+
205+
it('returns a URL object for valid URLs', () => {
206+
expect(parseStringToURL('https://somedomain.com')).toBeInstanceOf(URL);
207+
});
208+
209+
it('does not throw an error if URl.canParse is not defined', () => {
210+
const canParse = (URL as any).canParse;
211+
delete (URL as any).canParse;
212+
expect(parseStringToURL('https://somedomain.com')).toBeInstanceOf(URL);
213+
(URL as any).canParse = canParse;
214+
});
215+
});

0 commit comments

Comments
 (0)