Skip to content

Commit 08dfb6f

Browse files
committed
integrate shared tracker
1 parent bbe0d77 commit 08dfb6f

File tree

3 files changed

+76
-47
lines changed

3 files changed

+76
-47
lines changed

packages/plugin-http-errors/http-errors.js

Lines changed: 51 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const DEFAULT_MAX_REQUEST_SIZE = 5000
1515
* @param {Function} config.onHttpError - Callback for intercepting HTTP errors
1616
* @returns {Object} Bugsnag plugin
1717
*/
18-
module.exports = (config = {}) => {
18+
module.exports = (config = {}, global = window) => {
1919
const {
2020
httpErrorCodes = DEFAULT_HTTP_ERROR_CODES,
2121
maxRequestSize = DEFAULT_MAX_REQUEST_SIZE,
@@ -106,36 +106,57 @@ module.exports = (config = {}) => {
106106
}
107107
}
108108

109-
return {
109+
let restoreFunctions = []
110+
const plugin = {
110111
name: 'httpErrors',
111112
load: (client) => {
112-
// Store original fetch
113-
const originalFetch = global.fetch
114-
115-
if (!originalFetch) {
116-
client._logger.warn('fetch is not available, HTTP errors plugin will not work')
117-
return
113+
// Try to get existing request tracker
114+
let requestTrackerPlugin = client.getPlugin('requestTracker')
115+
116+
// Auto-load request tracker if not present
117+
if (!requestTrackerPlugin) {
118+
try {
119+
const { createRequestTrackerPlugin } = require('@bugsnag/request-tracker')
120+
const trackerPlugin = createRequestTrackerPlugin([], global)
121+
client._loadPlugin(trackerPlugin)
122+
requestTrackerPlugin = client.getPlugin('requestTracker')
123+
} catch (error) {
124+
client._logger.warn('Failed to auto-load request tracker, using direct fetch patching:', error.message)
125+
}
118126
}
119127

120-
// Wrap fetch
121-
global.fetch = async function wrappedFetch (input, init = {}) {
122-
const response = await originalFetch.call(this, input, init)
128+
// Use shared request tracker if available
129+
if (requestTrackerPlugin) {
130+
const { fetchTracker } = requestTrackerPlugin
131+
132+
if (fetchTracker) {
133+
restoreFunctions.push(fetchTracker._restore)
134+
fetchTracker.onStart((startContext) => {
135+
return {
136+
onRequestEnd: (endContext) => {
137+
handleHttpError(startContext, endContext)
138+
}
139+
}
140+
})
141+
}
142+
}
123143

144+
function handleHttpError (startContext, endContext) {
124145
// Check if we should capture this status code
125-
if (!shouldCaptureStatusCode(response.status)) {
126-
return response
146+
if (!shouldCaptureStatusCode(endContext.status)) {
147+
return
127148
}
128149

129150
// Extract request information
130-
const url = typeof input === 'string' ? input : input.url
131-
const method = (init.method || 'GET').toUpperCase()
151+
const url = startContext.url
152+
const method = startContext.method
132153
const domain = extractDomain(url)
133154

134155
// Extract request body
135156
let requestBody = ''
136157
let requestBodyLength = 0
137-
if (init.body) {
138-
const bodyStr = typeof init.body === 'string' ? init.body : String(init.body)
158+
if (startContext.init && startContext.init.body) {
159+
const bodyStr = typeof startContext.init.body === 'string' ? startContext.init.body : String(startContext.init.body)
139160
requestBodyLength = bodyStr.length
140161
requestBody = truncate(bodyStr, maxRequestSize)
141162
}
@@ -144,15 +165,15 @@ module.exports = (config = {}) => {
144165
const requestObj = {
145166
url,
146167
httpMethod: method,
147-
headers: headersToObject(init.headers),
168+
headers: headersToObject(startContext.init && startContext.init.headers),
148169
params: parseQueryParams(url),
149170
body: requestBody,
150171
bodyLength: requestBodyLength
151172
}
152173

153174
const responseObj = {
154-
statusCode: response.status,
155-
headers: headersToObject(response.headers)
175+
statusCode: endContext.status,
176+
headers: headersToObject(endContext.response && endContext.response.headers)
156177
}
157178

158179
// Call onHttpError callback if provided
@@ -161,7 +182,7 @@ module.exports = (config = {}) => {
161182

162183
// If onHttpError returns false, don't capture
163184
if (result === false) {
164-
return response
185+
return
165186
}
166187
}
167188

@@ -188,12 +209,16 @@ module.exports = (config = {}) => {
188209
event.context = `${method} ${domain}`
189210

190211
client._notify(event)
191-
192-
return response
193212
}
194213
}
195214
}
196-
}
197215

198-
// add a default export for ESM modules without interop
199-
module.exports.default = module.exports
216+
if (process.env.NODE_ENV !== 'production') {
217+
plugin.destroy = () => {
218+
restoreFunctions.forEach(fn => fn())
219+
restoreFunctions = []
220+
}
221+
}
222+
223+
return plugin
224+
}

packages/plugin-http-errors/test/http-errors.test.ts

Lines changed: 23 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import Client, { Delivery } from '@bugsnag/core/client'
22
import createPlugin from '..'
33
import Event from '@bugsnag/core/event'
4+
import { Plugin } from '@bugsnag/core'
45

56
// Mock fetch globally
67
const originalFetch = global.fetch
@@ -27,6 +28,7 @@ const createMockDelivery = (notifyCallbacks: Event[]) => (): Delivery => ({
2728

2829
describe('plugin-http-errors', () => {
2930
let mockFetch: jest.Mock
31+
let plugin: Plugin
3032

3133
beforeEach(() => {
3234
mockFetch = jest.fn()
@@ -36,17 +38,18 @@ describe('plugin-http-errors', () => {
3638
afterEach(() => {
3739
global.fetch = originalFetch
3840
jest.clearAllMocks()
41+
plugin.destroy?.()
3942
})
4043

4144
describe('plugin configuration', () => {
4245
it('should export a plugin with name and load function', () => {
43-
const plugin = createPlugin()
46+
plugin = createPlugin()
4447
expect(plugin.name).toBe('httpErrors')
4548
expect(typeof plugin.load).toBe('function')
4649
})
4750

4851
it('should load without configuration', () => {
49-
const plugin = createPlugin()
52+
plugin = createPlugin()
5053
const client = new Client({ apiKey: 'api_key', plugins: [plugin] })
5154
expect(client).toBeDefined()
5255
})
@@ -56,7 +59,7 @@ describe('plugin-http-errors', () => {
5659
it('should capture 4xx errors when configured with single range', async () => {
5760
const notifyCallbacks: Event[] = []
5861

59-
const plugin = createPlugin({
62+
plugin = createPlugin({
6063
httpErrorCodes: { min: 400, max: 499 }
6164
})
6265

@@ -95,7 +98,7 @@ describe('plugin-http-errors', () => {
9598
it('should not capture 2xx successful responses', async () => {
9699
const notifyCallbacks: Event[] = []
97100

98-
const plugin = createPlugin({
101+
plugin = createPlugin({
99102
httpErrorCodes: { min: 400, max: 499 }
100103
})
101104

@@ -120,7 +123,7 @@ describe('plugin-http-errors', () => {
120123
it('should not capture 5xx errors when configured for 4xx only', async () => {
121124
const notifyCallbacks: Event[] = []
122125

123-
const plugin = createPlugin({
126+
plugin = createPlugin({
124127
httpErrorCodes: { min: 400, max: 499 }
125128
})
126129

@@ -147,7 +150,7 @@ describe('plugin-http-errors', () => {
147150
it('should capture errors matching any range or specific code', async () => {
148151
const notifyCallbacks: Event[] = []
149152

150-
const plugin = createPlugin({
153+
plugin = createPlugin({
151154
httpErrorCodes: [{ min: 400, max: 499 }, 500, 503]
152155
})
153156

@@ -213,7 +216,7 @@ describe('plugin-http-errors', () => {
213216
it('should capture all 4xx and 5xx errors by default', async () => {
214217
const notifyCallbacks: Event[] = []
215218

216-
const plugin = createPlugin() // No config
219+
plugin = createPlugin() // No config
217220

218221
const client = new Client({ apiKey: 'api_key', plugins: [plugin] })
219222
client._setDelivery(createMockDelivery(notifyCallbacks))
@@ -248,7 +251,7 @@ describe('plugin-http-errors', () => {
248251
it('should truncate request body when it exceeds maxRequestSize', async () => {
249252
const notifyCallbacks: Event[] = []
250253

251-
const plugin = createPlugin({
254+
plugin = createPlugin({
252255
httpErrorCodes: { min: 400, max: 499 },
253256
maxRequestSize: 50
254257
})
@@ -282,7 +285,7 @@ describe('plugin-http-errors', () => {
282285
it('should use default maxRequestSize of 5000 when not specified', async () => {
283286
const notifyCallbacks: Event[] = []
284287

285-
const plugin = createPlugin({
288+
plugin = createPlugin({
286289
httpErrorCodes: { min: 400, max: 499 }
287290
})
288291

@@ -318,7 +321,7 @@ describe('plugin-http-errors', () => {
318321
const onHttpError = jest.fn()
319322
const notifyCallbacks: Event[] = []
320323

321-
const plugin = createPlugin({
324+
plugin = createPlugin({
322325
httpErrorCodes: { min: 400, max: 499 },
323326
onHttpError
324327
})
@@ -348,7 +351,7 @@ describe('plugin-http-errors', () => {
348351
it('should prevent event creation when onHttpError returns false', async () => {
349352
const notifyCallbacks: Event[] = []
350353

351-
const plugin = createPlugin({
354+
plugin = createPlugin({
352355
httpErrorCodes: { min: 400, max: 499 },
353356
onHttpError: ({ request }) => {
354357
// Filter out requests with PII
@@ -389,7 +392,7 @@ describe('plugin-http-errors', () => {
389392
it('should allow onHttpError to modify request and response', async () => {
390393
const notifyCallbacks: Event[] = []
391394

392-
const plugin = createPlugin({
395+
plugin = createPlugin({
393396
httpErrorCodes: { min: 400, max: 499 },
394397
onHttpError: ({ request, response }) => {
395398
// Redact sensitive information
@@ -425,7 +428,7 @@ describe('plugin-http-errors', () => {
425428
it('should filter errors by status code in onHttpError', async () => {
426429
const notifyCallbacks: Event[] = []
427430

428-
const plugin = createPlugin({
431+
plugin = createPlugin({
429432
httpErrorCodes: [{ min: 400, max: 599 }],
430433
onHttpError: ({ response }) => {
431434
// Only handle 5xx errors
@@ -468,7 +471,7 @@ describe('plugin-http-errors', () => {
468471
it('should populate event.errors with correct structure', async () => {
469472
const notifyCallbacks: Event[] = []
470473

471-
const plugin = createPlugin({
474+
plugin = createPlugin({
472475
httpErrorCodes: { min: 400, max: 499 }
473476
})
474477

@@ -498,7 +501,7 @@ describe('plugin-http-errors', () => {
498501
it('should set event.context to method and domain', async () => {
499502
const notifyCallbacks: Event[] = []
500503

501-
const plugin = createPlugin({
504+
plugin = createPlugin({
502505
httpErrorCodes: { min: 400, max: 499 }
503506
})
504507

@@ -524,7 +527,7 @@ describe('plugin-http-errors', () => {
524527
it('should populate request metadata with all fields', async () => {
525528
const notifyCallbacks: Event[] = []
526529

527-
const plugin = createPlugin({
530+
plugin = createPlugin({
528531
httpErrorCodes: { min: 400, max: 499 }
529532
})
530533

@@ -563,7 +566,7 @@ describe('plugin-http-errors', () => {
563566
it('should populate response metadata with all fields', async () => {
564567
const notifyCallbacks: Event[] = []
565568

566-
const plugin = createPlugin({
569+
plugin = createPlugin({
567570
httpErrorCodes: { min: 400, max: 499 }
568571
})
569572

@@ -595,7 +598,7 @@ describe('plugin-http-errors', () => {
595598
it('should handle requests with different HTTP methods', async () => {
596599
const notifyCallbacks: Event[] = []
597600

598-
const plugin = createPlugin({
601+
plugin = createPlugin({
599602
httpErrorCodes: { min: 400, max: 499 }
600603
})
601604

@@ -628,7 +631,7 @@ describe('plugin-http-errors', () => {
628631
it('should handle URLs with ports', async () => {
629632
const notifyCallbacks: Event[] = []
630633

631-
const plugin = createPlugin({
634+
plugin = createPlugin({
632635
httpErrorCodes: { min: 400, max: 499 }
633636
})
634637

@@ -655,7 +658,7 @@ describe('plugin-http-errors', () => {
655658
it('should handle requests without query parameters', async () => {
656659
const notifyCallbacks: Event[] = []
657660

658-
const plugin = createPlugin({
661+
plugin = createPlugin({
659662
httpErrorCodes: { min: 400, max: 499 }
660663
})
661664

tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@
9797
"packages/plugin-console-breadcrumbs",
9898
"packages/plugin-browser-session",
9999
"packages/browser",
100-
"packages/request-tracker"
100+
"packages/request-tracker",
101+
"packages/plugin-http-errors"
101102
],
102103
"exclude": [
103104
"packages/react-native/src/NativeBugsnag.ts"

0 commit comments

Comments
 (0)