Skip to content

Commit fdba07e

Browse files
authored
Manually parse urls (#2674)
* test: ✅ fix react native test, removing need for polyfill * refactor: ♻️ update query parameter parsing * remove use of url.search * remove URLSearchParams * Find the earliest occurrence of '/', '?', or '#' to determine the domain boundary * docs: add CHANGELOG entry
1 parent 0cbf143 commit fdba07e

10 files changed

Lines changed: 68 additions & 46 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
### Changed
66

7+
(plugin-network-instrumentation) Manually parse URLs to improve React Native compatibility [#2674](https://github.com/bugsnag/bugsnag-js/pull/2674)
78
Update bugsnag-android to [v6.22.0](https//github.com/bugsnag/bugsnag-android/releases/tag/v6.22.0) [#2656](https://github.com/bugsnag/bugsnag-js/pull/2656)
89

910
### Fixed

packages/node/test/notifier.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ describe('node notifier', () => {
44
beforeAll(() => {
55
jest.spyOn(console, 'debug').mockImplementation(() => {})
66
jest.spyOn(console, 'warn').mockImplementation(() => {})
7+
jest.spyOn(console, 'log').mockImplementation(() => {})
78
})
89

910
beforeEach(() => {

packages/plugin-aws-lambda/test/serverless-express.test.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ let sentEvents
1414
let sentSessions
1515

1616
describe('serverless express', function () {
17+
beforeAll(() => {
18+
jest.spyOn(console, 'debug').mockImplementation(() => {})
19+
})
20+
1721
beforeEach(function () {
1822
sentEvents = []
1923
sentSessions = []

packages/plugin-network-instrumentation/lib/extract-domain.js

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,29 @@
55
*/
66
module.exports = function (url) {
77
try {
8-
const urlObj = new URL(url)
9-
return urlObj.host
8+
const isAbsolute = /^https?:\/\//i.test(url)
9+
if (!isAbsolute) {
10+
return 'unknown'
11+
}
12+
13+
const urlWithoutProtocol = url.replace(/^https?:\/\//i, '')
14+
15+
// Find the earliest occurrence of '/', '?', or '#' to determine the domain boundary
16+
const slashIndex = urlWithoutProtocol.indexOf('/')
17+
const queryIndex = urlWithoutProtocol.indexOf('?')
18+
const hashIndex = urlWithoutProtocol.indexOf('#')
19+
let endIndex = urlWithoutProtocol.length
20+
if (slashIndex !== -1 && slashIndex < endIndex) {
21+
endIndex = slashIndex
22+
}
23+
if (queryIndex !== -1 && queryIndex < endIndex) {
24+
endIndex = queryIndex
25+
}
26+
if (hashIndex !== -1 && hashIndex < endIndex) {
27+
endIndex = hashIndex
28+
}
29+
30+
return urlWithoutProtocol.substring(0, endIndex)
1031
} catch (e) {
1132
return 'unknown'
1233
}

packages/plugin-network-instrumentation/lib/parse-query-params.js

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,24 @@
55
*/
66
module.exports = function (url) {
77
try {
8-
const urlObj = new URL(url)
9-
const params = {}
10-
urlObj.searchParams.forEach((value, key) => {
11-
params[key] = value
8+
const queryStart = url.indexOf('?')
9+
const hashStart = url.indexOf('#')
10+
11+
let queryString = ''
12+
if (queryStart !== -1) {
13+
const queryEnd = hashStart !== -1 && hashStart > queryStart ? hashStart : url.length
14+
queryString = url.substring(queryStart + 1, queryEnd)
15+
}
16+
17+
// convert query string to object without using UrlSearchParams
18+
const queryStringObject = {}
19+
const pairs = queryString.split('&').filter(pair => pair.length > 0)
20+
pairs.forEach(pair => {
21+
const [key, value] = pair.split('=')
22+
queryStringObject[decodeURIComponent(key)] = decodeURIComponent(value || '')
1223
})
13-
return params
24+
25+
return queryStringObject
1426
} catch (e) {
1527
return {}
1628
}
Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,23 @@
1+
const parseQueryParams = require('./parse-query-params')
12
const redactValues = require('./redact-values')
23

3-
function isAbsoluteURL (url) {
4-
try {
5-
// eslint-disable-next-line no-new
6-
new URL(url)
7-
return true
8-
} catch (e) {
9-
return false
10-
}
11-
}
12-
134
module.exports = function (url, redactedKeys) {
14-
const isAbsolute = isAbsoluteURL(url)
15-
const base = isAbsolute ? undefined : 'http://localhost'
16-
17-
// Parse the URL - use a base only for relative URLs
18-
const urlObj = new URL(url, base)
19-
const params = new URLSearchParams(urlObj.search)
20-
21-
// Convert URLSearchParams to object without using Object.fromEntries()
22-
const paramsObject = {}
23-
params.forEach((value, key) => {
24-
paramsObject[key] = value
25-
})
26-
5+
const paramsObject = parseQueryParams(url)
276
const redactedParams = redactValues(paramsObject, redactedKeys)
28-
urlObj.search = new URLSearchParams(redactedParams).toString()
7+
const redactedQueryString = Object.entries(redactedParams).map(([key, value]) => `${key}=${value}`).join('&')
298

30-
// Return appropriate format based on original URL type
31-
if (isAbsolute) {
32-
return decodeURI(urlObj.toString())
9+
const queryStart = url.indexOf('?')
10+
const hashStart = url.indexOf('#')
11+
const hash = hashStart !== -1 ? url.substring(hashStart) : ''
12+
let result = queryStart !== -1 ? url.substring(0, queryStart) : url
13+
14+
// Build the result URL manually
15+
if (redactedQueryString && redactedQueryString.length > 0) {
16+
result += '?' + redactedQueryString
17+
}
18+
if (hash) {
19+
result += hash
3320
}
3421

35-
// For relative URLs, return only the path + search + hash components
36-
const relativePart = urlObj.pathname + urlObj.search + urlObj.hash
37-
return decodeURI(relativePart)
22+
return result
3823
}

test/browser/features/http_errors.feature

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Feature: HTTP Errors
1616
And the exception "errorClass" equals "HTTPError"
1717
And the error payload field "events.0.exceptions.0.message" equals the stored value "expected.exception.message"
1818
And the event "severity" equals "error"
19-
And the event "unhandled" is true
19+
And the event "unhandled" is false
2020
And the event "severityReason.type" equals "httpError"
2121
And the error payload field "events.0.context" equals the stored value "expected.context"
2222

@@ -46,7 +46,7 @@ Feature: HTTP Errors
4646
And the exception "errorClass" equals "HTTPError"
4747
And the error payload field "events.0.exceptions.0.message" equals the stored value "expected.exception.message"
4848
And the event "severity" equals "error"
49-
And the event "unhandled" is true
49+
And the event "unhandled" is false
5050
And the event "severityReason.type" equals "httpError"
5151
And the error payload field "events.0.context" equals the stored value "expected.context"
5252

@@ -78,7 +78,7 @@ Feature: HTTP Errors
7878
And the exception "errorClass" equals "HTTPError"
7979
And the error payload field "events.0.exceptions.0.message" equals the stored value "expected.exception.message"
8080
And the event "severity" equals "error"
81-
And the event "unhandled" is true
81+
And the event "unhandled" is false
8282
And the event "severityReason.type" equals "httpError"
8383
And the error payload field "events.0.context" equals the stored value "expected.context"
8484

@@ -109,7 +109,7 @@ Feature: HTTP Errors
109109
And the exception "errorClass" equals "HTTPError"
110110
And the error payload field "events.0.exceptions.0.message" equals the stored value "expected.exception.message"
111111
And the event "severity" equals "error"
112-
And the event "unhandled" is true
112+
And the event "unhandled" is false
113113
And the event "severityReason.type" equals "httpError"
114114
And the error payload field "events.0.context" equals the stored value "expected.context"
115115

test/react-native/features/fixtures/expected_http_errors/401.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[
22
{
3-
"message": "^401: .*\/reflect\/[?]status=401$",
3+
"message": "^401: .*\/reflect[?]status=401$",
44
"errorClass": "HTTPError",
55
"type": "reactnativejs",
66
"stacktrace": "IGNORE"

test/react-native/features/fixtures/expected_http_errors/500.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[
22
{
3-
"message": "^500: .*\/reflect\/[?]status=500$",
3+
"message": "^500: .*\/reflect[?]status=500$",
44
"errorClass": "HTTPError",
55
"type": "reactnativejs",
66
"stacktrace": "IGNORE"

test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkRequestScenario.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ export class NetworkRequestScenario extends Scenario {
1818
}
1919

2020
run () {
21-
const url = new URL(this.reflectEndpoint)
22-
url.searchParams.append('status', this.statusCode)
23-
fetch(url).catch((err) => {
21+
fetch(`${this.reflectEndpoint}?status=${this.statusCode}`).catch((err) => {
2422
Bugsnag.notify(err)
2523
})
2624
}

0 commit comments

Comments
 (0)