Skip to content

Commit 3caa7da

Browse files
authored
Merge pull request #2667 from bugsnag/react-native-delivery-request-response
Handle request and response objects in @bugsnag/delivery-react-native package
1 parent 5490eed commit 3caa7da

24 files changed

Lines changed: 413 additions & 168 deletions

.buildkite/package_manifest.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
"packages/plugin-interaction-breadcrumbs",
2121
"packages/plugin-navigation-breadcrumbs",
2222
"packages/plugin-network-breadcrumbs",
23+
"packages/plugin-network-instrumentation",
2324
"packages/plugin-simple-throttle",
2425
"packages/plugin-strip-query-string",
2526
"packages/plugin-window-onerror",
@@ -97,6 +98,7 @@
9798
"packages/delivery-react-native",
9899
"packages/plugin-console-breadcrumbs",
99100
"packages/plugin-network-breadcrumbs",
101+
"packages/plugin-network-instrumentation",
100102
"packages/plugin-react",
101103
"packages/plugin-react-native-client-sync",
102104
"packages/plugin-react-native-event-sync",
@@ -121,6 +123,7 @@
121123
"packages/delivery-react-native",
122124
"packages/plugin-console-breadcrumbs",
123125
"packages/plugin-network-breadcrumbs",
126+
"packages/plugin-network-instrumentation",
124127
"packages/plugin-react",
125128
"packages/plugin-react-native-client-sync",
126129
"packages/plugin-react-native-event-sync",

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@
22

33
## [Unreleased]
44

5+
### Added
6+
7+
- (delivery-react-native) Handle request and response parameters [#2667](https://github.com/bugsnag/bugsnag-js/pull/2667)
8+
59
### Changed
610

711
- (plugin-inline-script-content) Add support for additional event target constructors including MediaSource, MediaRecorder, ServiceWorker, RTCPeerConnection, and others [#2700](https://github.com/bugsnag/bugsnag-js/pull/2700)
812
- (plugin-network-instrumentation) Manually parse URLs to improve React Native compatibility [#2674](https://github.com/bugsnag/bugsnag-js/pull/2674)
13+
- (plugin-network-instrumentation) Refactor URL parsing to improve performance [#2667](https://github.com/bugsnag/bugsnag-js/pull/2667)
914

1015
### Fixed
1116

packages/delivery-react-native/delivery.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ module.exports = (client, NativeClient) => ({
4848
breadcrumbs: derecursify(event.breadcrumbs),
4949
context: event.context,
5050
user: event._user,
51+
request: event.request,
52+
response: event.response,
5153
metadata: derecursify(event._metadata),
5254
groupingHash: event.groupingHash,
5355
groupingDiscriminator: event._groupingDiscriminator,

packages/delivery-react-native/test/delivery.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ type NativeClientEvent = Pick<EventWithInternals,
1717
| 'unhandled'
1818
| 'app'
1919
| 'device'
20+
| 'request'
21+
| 'response'
2022
| 'threads'
2123
| 'breadcrumbs'
2224
| 'context'
@@ -89,6 +91,8 @@ describe('delivery: react native', () => {
8991
expect(sent[0].context).toBe('test screen')
9092
expect(sent[0].user).toEqual({ id: '123', email: undefined, name: undefined })
9193
expect(sent[0].metadata).toEqual({})
94+
expect(sent[0].request).toEqual({})
95+
expect(sent[0].response).toEqual({})
9296
expect(sent[0].groupingHash).toEqual('ER_GRP_098')
9397
expect(sent[0].apiKey).toBe('abcdef123456abcdef123456abcdef123456')
9498
expect(sent[0].correlation).toEqual({ traceId: 'trace-id', spanId: 'span-id' })
@@ -286,6 +290,8 @@ describe('delivery: react native', () => {
286290
expect(sent[0].context).toBe('test screen')
287291
expect(sent[0].user).toEqual({ id: '123', email: undefined, name: undefined })
288292
expect(sent[0].metadata).toEqual({})
293+
expect(sent[0].request).toEqual({})
294+
expect(sent[0].response).toEqual({})
289295
expect(sent[0].groupingHash).toEqual('ER_GRP_098')
290296
expect(sent[0].apiKey).toBe('abcdef123456abcdef123456abcdef123456')
291297
done()

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

Lines changed: 0 additions & 34 deletions
This file was deleted.

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

Lines changed: 0 additions & 29 deletions
This file was deleted.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Parse a query string into an object
3+
* @param {string} queryString - Query string (e.g., "key=value&foo=bar")
4+
* @returns {Object<string, string>} Parsed query parameters as key-value pairs
5+
*/
6+
module.exports = function (queryString) {
7+
const params = {}
8+
if (!queryString) {
9+
return params
10+
}
11+
12+
const pairs = queryString.split('&').filter(pair => pair.length > 0)
13+
pairs.forEach(pair => {
14+
const [key, value] = pair.split('=')
15+
params[decodeURIComponent(key)] = decodeURIComponent(value || '')
16+
})
17+
18+
return params
19+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/**
2+
* Parse a URL in a single pass to extract domain, clean URL, and query string
3+
* @param {string} url - URL string
4+
* @returns {{ domain: string, cleanUrl: string, queryString: string }} Object with domain, cleanUrl, and queryString
5+
*/
6+
module.exports = function (url) {
7+
try {
8+
const isAbsolute = /^https?:\/\//i.test(url)
9+
10+
// Extract query string from the full URL
11+
const queryStart = url.indexOf('?')
12+
const hashStart = url.indexOf('#')
13+
14+
let queryString = ''
15+
if (queryStart !== -1) {
16+
const queryEnd = hashStart !== -1 && hashStart > queryStart ? hashStart : url.length
17+
queryString = url.substring(queryStart + 1, queryEnd)
18+
}
19+
20+
// Extract domain
21+
let domain = 'unknown'
22+
if (isAbsolute) {
23+
const urlWithoutProtocol = url.replace(/^https?:\/\//i, '')
24+
25+
// Find the earliest occurrence of '/', '?', or '#' to determine the domain boundary
26+
const slashIndex = urlWithoutProtocol.indexOf('/')
27+
const domainQueryIndex = urlWithoutProtocol.indexOf('?')
28+
const domainHashIndex = urlWithoutProtocol.indexOf('#')
29+
let endIndex = urlWithoutProtocol.length
30+
if (slashIndex !== -1 && slashIndex < endIndex) {
31+
endIndex = slashIndex
32+
}
33+
if (domainQueryIndex !== -1 && domainQueryIndex < endIndex) {
34+
endIndex = domainQueryIndex
35+
}
36+
if (domainHashIndex !== -1 && domainHashIndex < endIndex) {
37+
endIndex = domainHashIndex
38+
}
39+
40+
domain = urlWithoutProtocol.substring(0, endIndex)
41+
}
42+
43+
// Strip query string while preserving hash
44+
const hash = hashStart !== -1 ? url.substring(hashStart) : ''
45+
let urlWithoutHash = queryStart !== -1 ? url.substring(0, queryStart) : url
46+
if (hashStart !== -1 && queryStart === -1) {
47+
// If there's a hash but no query string, remove the hash first
48+
urlWithoutHash = url.substring(0, hashStart)
49+
}
50+
const cleanUrl = urlWithoutHash + hash
51+
52+
return {
53+
domain,
54+
cleanUrl,
55+
queryString
56+
}
57+
} catch (e) {
58+
return {
59+
domain: 'unknown',
60+
cleanUrl: url,
61+
queryString: ''
62+
}
63+
}
64+
}

packages/plugin-network-instrumentation/lib/redact-query-parameters.js

Lines changed: 0 additions & 23 deletions
This file was deleted.

packages/plugin-network-instrumentation/network-instrumentation.js

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
* A plugin to automatically capture and report HTTP errors
44
*/
55

6-
const extractDomain = require('./lib/extract-domain')
7-
const parseQueryParams = require('./lib/parse-query-params')
8-
const redactQueryParameters = require('./lib/redact-query-parameters')
6+
const parseUrl = require('./lib/parse-url')
7+
const parseQueryString = require('./lib/parse-query-string')
8+
const redactValues = require('./lib/redact-values')
99
const shouldCaptureStatusCode = require('./lib/should-capture-status-code')
1010
const truncate = require('./lib/truncate')
1111

@@ -84,22 +84,25 @@ module.exports = (config = {}, global = window) => {
8484

8585
try {
8686
// Extract request information
87-
const url = startContext.url
88-
const requestParams = parseQueryParams(url)
87+
const originalUrl = startContext.url
88+
const { domain, cleanUrl, queryString } = parseUrl(originalUrl)
89+
const url = cleanUrl
8990
const method = startContext.method
90-
const domain = extractDomain(url)
91+
92+
// Parse query string into object
93+
const requestParams = parseQueryString(queryString)
9194

9295
// Create request and response objects for callback
9396
const requestObj = {
94-
url: startContext.url,
95-
httpMethod: startContext.method,
97+
url,
98+
httpMethod: method,
9699
headers: startContext.headers,
97-
params: requestParams
100+
params: redactValues(requestParams, client._config.redactedKeys),
101+
bodyLength: startContext.body ? startContext.body.length : undefined
98102
}
99103
const responseObj = {
100104
statusCode: endContext.status,
101105
headers: endContext.headers,
102-
body: endContext.body,
103106
bodyLength: endContext.body ? endContext.body.length : undefined
104107
}
105108

@@ -116,22 +119,15 @@ module.exports = (config = {}, global = window) => {
116119
// Truncate request body
117120
if (maxRequestSize > 0 && startContext.body) {
118121
requestObj.body = truncate(startContext.body, maxRequestSize)
119-
requestObj.bodyLength = startContext.body.length
120122
}
121123

122124
// Truncate response body - XHR only
123125
if (maxResponseSize > 0 && endContext.body) {
124126
responseObj.body = truncate(endContext.body, maxResponseSize)
125-
responseObj.bodyLength = endContext.body.length
126-
}
127-
128-
// Strip query parameters from URL
129-
if (requestObj.url !== '[REDACTED]') {
130-
requestObj.url = redactQueryParameters(requestObj.url, client._config.redactedKeys)
131127
}
132128

133129
// Create error and notify
134-
const error = new Error(`${responseObj.statusCode}: ${requestObj.url}`)
130+
const error = new Error(`${responseObj.statusCode}: ${url}`)
135131
error.name = 'HTTPError'
136132

137133
const handledState = {

0 commit comments

Comments
 (0)