Skip to content

Commit

Permalink
set Bugsnag-Integrity header in delivery-fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Skinner committed Oct 15, 2024
1 parent 50b432a commit 14dca7e
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 15 deletions.
4 changes: 3 additions & 1 deletion jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ module.exports = {
],
projects: [
project('core', ['core']),
project('web workers', ['web-worker']),
project('web workers', ['web-worker'], {
testEnvironment: './jest/FixJSDOMEnvironment.js'
}),
project('shared plugins', ['plugin-app-duration', 'plugin-stackframe-path-normaliser']),
project('browser', [
'browser',
Expand Down
18 changes: 18 additions & 0 deletions jest/FixJSDOMEnvironment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const { TextDecoder, TextEncoder } = require('node:util')
const crypto = require('crypto')

const JSDOMEnvironment = require('jest-environment-jsdom')

class FixJSDOMEnvironment extends JSDOMEnvironment {
constructor (...args) {
super(...args)

this.global.TextEncoder = TextEncoder
this.global.TextDecoder = TextDecoder
this.global.crypto = {
subtle: crypto.webcrypto.subtle
}
}
}

module.exports = FixJSDOMEnvironment
40 changes: 29 additions & 11 deletions packages/delivery-fetch/delivery.js
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
import payload from '@bugsnag/core/lib/json-payload'

const delivery = (client, fetch = global.fetch) => ({
async function addIntegrityHeader (windowOrWorkerGlobalScope, requestBody, headers) {
if (windowOrWorkerGlobalScope.isSecureContext && windowOrWorkerGlobalScope.crypto && windowOrWorkerGlobalScope.crypto.subtle && windowOrWorkerGlobalScope.crypto.subtle.digest && typeof TextEncoder === 'function') {
const msgUint8 = new TextEncoder().encode(requestBody)
const hashBuffer = await windowOrWorkerGlobalScope.crypto.subtle.digest('SHA-1', msgUint8)
const hashArray = Array.from(new Uint8Array(hashBuffer))
const hashHex = hashArray
.map((b) => b.toString(16).padStart(2, '0'))
.join('')

headers['Bugsnag-Integrity'] = 'sha1 ' + hashHex
}
}

const delivery = (client, fetch = global.fetch, windowOrWorkerGlobalScope = window) => ({
sendEvent: (event, cb = () => {}) => {
const url = client._config.endpoints.notify

fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Bugsnag-Api-Key': event.apiKey || client._config.apiKey,
'Bugsnag-Payload-Version': '4',
'Bugsnag-Sent-At': (new Date()).toISOString()
},
body: payload.event(event, client._config.redactedKeys)
}).then(() => {
const body = payload.event(event, client._config.redactedKeys)
const headers = {
'Content-Type': 'application/json',
'Bugsnag-Api-Key': event.apiKey || client._config.apiKey,
'Bugsnag-Payload-Version': '4',
'Bugsnag-Sent-At': (new Date()).toISOString()
}

addIntegrityHeader(windowOrWorkerGlobalScope, body, headers).then(() =>
fetch(url, {
method: 'POST',
headers,
body
})
).then(() => {
cb(null)
}).catch(err => {
client._logger.error(err)
Expand Down
29 changes: 27 additions & 2 deletions packages/delivery-fetch/test/delivery.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ const globalAny: any = global

describe('delivery:fetch', () => {
it('sends events successfully', done => {
window.isSecureContext = true

globalAny.fetch = jest.fn(() => Promise.resolve({
json: () => Promise.resolve()
}))
Expand All @@ -18,7 +20,7 @@ describe('delivery:fetch', () => {

const payload = { sample: 'payload' } as unknown as EventDeliveryPayload

delivery({ logger: {}, _config: config } as unknown as Client).sendEvent(payload, (err) => {
delivery({ logger: { }, _config: config } as unknown as Client).sendEvent(payload, (err) => {
expect(err).toBeNull()
expect(globalAny.fetch).toHaveBeenCalled()
expect(globalAny.fetch).toHaveBeenCalledWith('/echo/', expect.objectContaining({
Expand All @@ -28,11 +30,34 @@ describe('delivery:fetch', () => {
'Bugsnag-Api-Key': 'aaaaaaaa',
'Bugsnag-Payload-Version': '4',
'Bugsnag-Sent-At': expect.stringMatching(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/),
'Content-Type': 'application/json'
'Content-Type': 'application/json',
'Bugsnag-Integrity': 'sha1 14faf2461b0519f9d9d62cfb8d79483fcc8f825c'
})
}))
done()
})

window.isSecureContext = false
})

it('omits the bugsnag integrity header when not in a secure context', done => {
globalAny.fetch = jest.fn(() => Promise.resolve({
json: () => Promise.resolve()
}))

const config = {
apiKey: 'aaaaaaaa',
endpoints: { notify: '/echo/' },
redactedKeys: []
}

const payload = { sample: 'payload' } as unknown as EventDeliveryPayload

delivery({ logger: { }, _config: config } as unknown as Client).sendEvent(payload, (err) => {
expect(err).toBeNull()
expect(globalAny.fetch.mock.calls[0][1].headers['Bugsnag-Integrity']).toBeUndefined()
done()
})
})

it('returns an error for failed event delivery', done => {
Expand Down
2 changes: 1 addition & 1 deletion packages/web-worker/src/notifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const Bugsnag = {
// configure a client with user supplied options
const bugsnag = new Client(opts, schema, internalPlugins, { name, version, url })

bugsnag._setDelivery(delivery)
bugsnag._setDelivery(client => delivery(client, undefined, self))

bugsnag._logger.debug('Loaded!')

Expand Down

0 comments on commit 14dca7e

Please sign in to comment.