diff --git a/CHANGELOG.md b/CHANGELOG.md index 286dbd0c9e..6193571f80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed +(plugin-network-instrumentation) Manually parse URLs to improve React Native compatibility [#2674](https://github.com/bugsnag/bugsnag-js/pull/2674) 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) ### Fixed diff --git a/packages/node/test/notifier.test.ts b/packages/node/test/notifier.test.ts index 1fa7cc4fed..45fb56f48f 100644 --- a/packages/node/test/notifier.test.ts +++ b/packages/node/test/notifier.test.ts @@ -4,6 +4,7 @@ describe('node notifier', () => { beforeAll(() => { jest.spyOn(console, 'debug').mockImplementation(() => {}) jest.spyOn(console, 'warn').mockImplementation(() => {}) + jest.spyOn(console, 'log').mockImplementation(() => {}) }) beforeEach(() => { diff --git a/packages/plugin-aws-lambda/test/serverless-express.test.js b/packages/plugin-aws-lambda/test/serverless-express.test.js index 0212eca34e..a39b3a30a2 100644 --- a/packages/plugin-aws-lambda/test/serverless-express.test.js +++ b/packages/plugin-aws-lambda/test/serverless-express.test.js @@ -14,6 +14,10 @@ let sentEvents let sentSessions describe('serverless express', function () { + beforeAll(() => { + jest.spyOn(console, 'debug').mockImplementation(() => {}) + }) + beforeEach(function () { sentEvents = [] sentSessions = [] diff --git a/packages/plugin-network-instrumentation/lib/extract-domain.js b/packages/plugin-network-instrumentation/lib/extract-domain.js index fd290943b5..010febc501 100644 --- a/packages/plugin-network-instrumentation/lib/extract-domain.js +++ b/packages/plugin-network-instrumentation/lib/extract-domain.js @@ -5,8 +5,29 @@ */ module.exports = function (url) { try { - const urlObj = new URL(url) - return urlObj.host + const isAbsolute = /^https?:\/\//i.test(url) + if (!isAbsolute) { + return 'unknown' + } + + const urlWithoutProtocol = url.replace(/^https?:\/\//i, '') + + // Find the earliest occurrence of '/', '?', or '#' to determine the domain boundary + const slashIndex = urlWithoutProtocol.indexOf('/') + const queryIndex = urlWithoutProtocol.indexOf('?') + const hashIndex = urlWithoutProtocol.indexOf('#') + let endIndex = urlWithoutProtocol.length + if (slashIndex !== -1 && slashIndex < endIndex) { + endIndex = slashIndex + } + if (queryIndex !== -1 && queryIndex < endIndex) { + endIndex = queryIndex + } + if (hashIndex !== -1 && hashIndex < endIndex) { + endIndex = hashIndex + } + + return urlWithoutProtocol.substring(0, endIndex) } catch (e) { return 'unknown' } diff --git a/packages/plugin-network-instrumentation/lib/parse-query-params.js b/packages/plugin-network-instrumentation/lib/parse-query-params.js index e9dc18a054..fabb4e5222 100644 --- a/packages/plugin-network-instrumentation/lib/parse-query-params.js +++ b/packages/plugin-network-instrumentation/lib/parse-query-params.js @@ -5,12 +5,24 @@ */ module.exports = function (url) { try { - const urlObj = new URL(url) - const params = {} - urlObj.searchParams.forEach((value, key) => { - params[key] = value + const queryStart = url.indexOf('?') + const hashStart = url.indexOf('#') + + let queryString = '' + if (queryStart !== -1) { + const queryEnd = hashStart !== -1 && hashStart > queryStart ? hashStart : url.length + queryString = url.substring(queryStart + 1, queryEnd) + } + + // convert query string to object without using UrlSearchParams + const queryStringObject = {} + const pairs = queryString.split('&').filter(pair => pair.length > 0) + pairs.forEach(pair => { + const [key, value] = pair.split('=') + queryStringObject[decodeURIComponent(key)] = decodeURIComponent(value || '') }) - return params + + return queryStringObject } catch (e) { return {} } diff --git a/packages/plugin-network-instrumentation/lib/redact-query-parameters.js b/packages/plugin-network-instrumentation/lib/redact-query-parameters.js index 3c69f97e08..2c36acde1f 100644 --- a/packages/plugin-network-instrumentation/lib/redact-query-parameters.js +++ b/packages/plugin-network-instrumentation/lib/redact-query-parameters.js @@ -1,38 +1,23 @@ +const parseQueryParams = require('./parse-query-params') const redactValues = require('./redact-values') -function isAbsoluteURL (url) { - try { - // eslint-disable-next-line no-new - new URL(url) - return true - } catch (e) { - return false - } -} - module.exports = function (url, redactedKeys) { - const isAbsolute = isAbsoluteURL(url) - const base = isAbsolute ? undefined : 'http://localhost' - - // Parse the URL - use a base only for relative URLs - const urlObj = new URL(url, base) - const params = new URLSearchParams(urlObj.search) - - // Convert URLSearchParams to object without using Object.fromEntries() - const paramsObject = {} - params.forEach((value, key) => { - paramsObject[key] = value - }) - + const paramsObject = parseQueryParams(url) const redactedParams = redactValues(paramsObject, redactedKeys) - urlObj.search = new URLSearchParams(redactedParams).toString() + const redactedQueryString = Object.entries(redactedParams).map(([key, value]) => `${key}=${value}`).join('&') - // Return appropriate format based on original URL type - if (isAbsolute) { - return decodeURI(urlObj.toString()) + const queryStart = url.indexOf('?') + const hashStart = url.indexOf('#') + const hash = hashStart !== -1 ? url.substring(hashStart) : '' + let result = queryStart !== -1 ? url.substring(0, queryStart) : url + + // Build the result URL manually + if (redactedQueryString && redactedQueryString.length > 0) { + result += '?' + redactedQueryString + } + if (hash) { + result += hash } - // For relative URLs, return only the path + search + hash components - const relativePart = urlObj.pathname + urlObj.search + urlObj.hash - return decodeURI(relativePart) + return result } diff --git a/test/browser/features/http_errors.feature b/test/browser/features/http_errors.feature index fef4138ee3..cdc381667f 100644 --- a/test/browser/features/http_errors.feature +++ b/test/browser/features/http_errors.feature @@ -16,7 +16,7 @@ Feature: HTTP Errors And the exception "errorClass" equals "HTTPError" And the error payload field "events.0.exceptions.0.message" equals the stored value "expected.exception.message" And the event "severity" equals "error" - And the event "unhandled" is true + And the event "unhandled" is false And the event "severityReason.type" equals "httpError" And the error payload field "events.0.context" equals the stored value "expected.context" @@ -46,7 +46,7 @@ Feature: HTTP Errors And the exception "errorClass" equals "HTTPError" And the error payload field "events.0.exceptions.0.message" equals the stored value "expected.exception.message" And the event "severity" equals "error" - And the event "unhandled" is true + And the event "unhandled" is false And the event "severityReason.type" equals "httpError" And the error payload field "events.0.context" equals the stored value "expected.context" @@ -78,7 +78,7 @@ Feature: HTTP Errors And the exception "errorClass" equals "HTTPError" And the error payload field "events.0.exceptions.0.message" equals the stored value "expected.exception.message" And the event "severity" equals "error" - And the event "unhandled" is true + And the event "unhandled" is false And the event "severityReason.type" equals "httpError" And the error payload field "events.0.context" equals the stored value "expected.context" @@ -109,7 +109,7 @@ Feature: HTTP Errors And the exception "errorClass" equals "HTTPError" And the error payload field "events.0.exceptions.0.message" equals the stored value "expected.exception.message" And the event "severity" equals "error" - And the event "unhandled" is true + And the event "unhandled" is false And the event "severityReason.type" equals "httpError" And the error payload field "events.0.context" equals the stored value "expected.context" diff --git a/test/react-native/features/fixtures/expected_http_errors/401.json b/test/react-native/features/fixtures/expected_http_errors/401.json index e719a18b29..ed32018bf4 100644 --- a/test/react-native/features/fixtures/expected_http_errors/401.json +++ b/test/react-native/features/fixtures/expected_http_errors/401.json @@ -1,6 +1,6 @@ [ { - "message": "^401: .*\/reflect\/[?]status=401$", + "message": "^401: .*\/reflect[?]status=401$", "errorClass": "HTTPError", "type": "reactnativejs", "stacktrace": "IGNORE" diff --git a/test/react-native/features/fixtures/expected_http_errors/500.json b/test/react-native/features/fixtures/expected_http_errors/500.json index ccbe396790..d65420dd36 100644 --- a/test/react-native/features/fixtures/expected_http_errors/500.json +++ b/test/react-native/features/fixtures/expected_http_errors/500.json @@ -1,6 +1,6 @@ [ { - "message": "^500: .*\/reflect\/[?]status=500$", + "message": "^500: .*\/reflect[?]status=500$", "errorClass": "HTTPError", "type": "reactnativejs", "stacktrace": "IGNORE" diff --git a/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkRequestScenario.js b/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkRequestScenario.js index f69b85eb72..1470610ced 100644 --- a/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkRequestScenario.js +++ b/test/react-native/features/fixtures/scenario-launcher/scenarios/core/NetworkRequestScenario.js @@ -18,9 +18,7 @@ export class NetworkRequestScenario extends Scenario { } run () { - const url = new URL(this.reflectEndpoint) - url.searchParams.append('status', this.statusCode) - fetch(url).catch((err) => { + fetch(`${this.reflectEndpoint}?status=${this.statusCode}`).catch((err) => { Bugsnag.notify(err) }) }