Skip to content

Commit

Permalink
Merge pull request #2025 from bugsnag/PLAT-11062/electron-proxy-auth-…
Browse files Browse the repository at this point in the history
…request-loop
  • Loading branch information
yousif-bugsnag authored Oct 24, 2023
2 parents 9c77611 + f8550dd commit 8cf6696
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 16 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

- (electron) Fix startup crash when using Electron v26+ on Linux [#2022](https://github.com/bugsnag/bugsnag-js/pull/2022)

- (electron) Fix unhandled secondary errors during delivery [#2025](https://github.com/bugsnag/bugsnag-js/pull/2025)

## 7.22.0 (2023-09-13)

### Changes
Expand Down
38 changes: 26 additions & 12 deletions packages/delivery-electron/delivery.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,43 @@ const PayloadDeliveryLoop = require('./payload-loop')
const NetworkStatus = require('@bugsnag/electron-network-status')

const delivery = (client, filestore, net, app) => {
const noop = () => {}

const send = (opts, body, cb) => {
const errorHandler = err => {
err.isRetryable = true
cb(err)
let errorCallback = (error, response) => {
// an error can still happen on both the request and response even after a response is received,
// so we noop on subsequent calls to ensure this is only handled once
errorCallback = noop

if (response) {
error.isRetryable = isRetryable(response.statusCode)
// do not retry oversized payloads regardless of status code
if (body.length > 10e5) {
client._logger.warn(`Discarding over-sized event (${(body.length / 10e5).toFixed(2)} MB) after failed delivery`)
error.isRetryable = false
}
} else {
error.isRetryable = true
}

cb(error)
}

const req = net.request(opts, response => {
req.removeListener('error', errorHandler)
// handle errors on the response stream
response.on('error', err => {
errorCallback(err, response)
})

if (isOk(response)) {
cb(null)
} else {
const err = new Error(`Bad status code from API: ${response.statusCode}`)
err.isRetryable = isRetryable(response.statusCode)
// do not retry oversized payloads regardless of status code
if (body.length > 10e5) {
client._logger.warn(`Discarding over-sized event (${(body.length / 10e5).toFixed(2)} MB) after failed delivery`)
err.isRetryable = false
}
cb(err)
errorCallback(err, response)
}
})

req.on('error', errorHandler)
req.on('error', err => errorCallback(err))

try {
req.write(body)
Expand Down
54 changes: 54 additions & 0 deletions packages/delivery-electron/test/delivery.test-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,60 @@ describe('delivery: electron', () => {
})
})

it('ignores secondary request/response errors after a response is received', done => {
const payload = {
events: [{ errors: [{ errorClass: 'Error', errorMessage: 'foo is not a function' }] }]
} as unknown as EventDeliveryPayload

const secondaryError = new Error('Error after response received')

// create a mock response object that emits an error event
const response = {
statusCode: 407,
statusMessage: STATUS_CODES[407],
on (event: any, cb: any) {
if (event === 'error') {
cb(secondaryError)
}
}
}

let requestErrorCallback: any = () => {}

// create a mock net instance that will return a response and also emit errors
const net = {
request: (opts: any, responseCallback: any) => ({
on (event: any, cb: any) {
if (event === 'error') {
requestErrorCallback = cb
}
},
write () {},
end () {
responseCallback(response)
requestErrorCallback(secondaryError)
}
})
}

const logger = { error: jest.fn(), info: jest.fn() }
const config = {
apiKey: 'aaaaaaaa',
endpoints: { notify: 'http://localhost:9999/events/' },
redactedKeys: []
}

delivery(filestore, net, app)(makeClient(config, logger)).sendEvent(payload, (err: any) => {
expect(err).toBe(secondaryError)
expect(err.isRetryable).toBe(false)
expect(logger.error).toHaveBeenCalledWith('event failed to send…\n', err.stack)
expect(logger.error).toHaveBeenCalledTimes(1)
expect(enqueueSpy).not.toHaveBeenCalled()

done()
})
})

it('handles uncaught exceptions during event delivery', done => {
const payload = {
events: [{ errors: [{ errorClass: 'Error', errorMessage: 'foo is not a function' }] }]
Expand Down
2 changes: 2 additions & 0 deletions packages/plugin-electron-deliver-minidumps/send-minidump.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ module.exports = (net, client) => {
const send = (opts, formData) => {
return new Promise((resolve, reject) => {
const req = net.request(opts, response => {
response.on('error', reject)

if (isOk(response)) {
resolve()
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ describe('electron-minidump-delivery: sendMinidump', () => {
it('sends minidump successfully', async () => {
const net = {
request: jest.fn().mockImplementation((_, handle) => {
handle({ statusCode: 200 })
handle({ statusCode: 200, on: (event, cb) => {} })
})
}

Expand All @@ -48,7 +48,7 @@ describe('electron-minidump-delivery: sendMinidump', () => {
it('marks server error as retry', async () => {
const net = {
request: jest.fn().mockImplementation((opts, handle) => {
handle({ statusCode: 500 })
handle({ statusCode: 500, on: (event, cb) => {} })
})
}

Expand All @@ -62,7 +62,7 @@ describe('electron-minidump-delivery: sendMinidump', () => {
it('marks bad request as no-retry', async () => {
const net = {
request: jest.fn().mockImplementation((opts, handle) => {
handle({ statusCode: 400 })
handle({ statusCode: 400, on: (event, cb) => {} })
})
}

Expand All @@ -77,7 +77,7 @@ describe('electron-minidump-delivery: sendMinidump', () => {
let minidumpFile
const net = {
request: jest.fn().mockImplementation((_, handle) => {
handle({ statusCode: 200 })
handle({ statusCode: 200, on: (event, cb) => {} })
})
}

Expand Down

0 comments on commit 8cf6696

Please sign in to comment.