Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 8 additions & 45 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions packages/plugin-node-device/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
"author": "Bugsnag",
"license": "MIT",
"devDependencies": {
"@bugsnag/core": "^8.4.0",
"@types/node": "^18.19.74"
"@bugsnag/core": "^8.4.0"
},
"peerDependencies": {
"@bugsnag/core": "^8.0.0"
Expand Down
19 changes: 16 additions & 3 deletions packages/plugin-node-unhandled-rejection/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
{
"name": "@bugsnag/plugin-node-unhandled-rejection",
"version": "8.4.0",
"main": "unhandled-rejection.js",
"main": "dist/unhandled-rejection.js",
"types": "dist/types/unhandled-rejection.d.ts",
"exports": {
".": {
"types": "./dist/types/unhandled-rejection.d.ts",
"default": "./dist/unhandled-rejection.js",
"import": "./dist/unhandled-rejection.mjs"
}
},
"description": "@bugsnag/js plugin to capture and report unhandled rejections",
"homepage": "https://www.bugsnag.com/",
"repository": {
Expand All @@ -12,15 +20,20 @@
"access": "public"
},
"files": [
"*.js"
"dist"
],
"scripts": {},
"author": "Bugsnag",
"license": "MIT",
"devDependencies": {
"@bugsnag/core": "^8.4.0"
},
"peerDependencies": {
"@bugsnag/core": "^8.0.0"
},
"scripts": {
"build": "npm run build:npm",
"build:npm": "rollup --config rollup.config.npm.mjs",
"clean": "rm -rf dist/*",
"test:types": "tsc -p tsconfig.json"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import createRollupConfig from "../../.rollup/index.mjs"

export default createRollupConfig({
input: "src/unhandled-rejection.ts",
external: [/node_modules/],
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { Client, Event, Logger, Plugin } from '@bugsnag/core'
import { AsyncLocalStorage } from 'async_hooks'

interface NodeConfig {
onUnhandledRejection: (err: Error, event: Event, logger: Logger) => void
reportUnhandledPromiseRejectionsAsHandled: boolean
}

interface InternalClient extends Client {
_clientContext: AsyncLocalStorage<Client>
_config: Client['_config'] & NodeConfig
}

// Type for the process with unhandledRejection event support
type ProcessWithUnhandledRejection = NodeJS.Process & {
prependListener(event: 'unhandledRejection', listener: (reason: any) => void): NodeJS.Process
on(event: 'unhandledRejection', listener: (reason: any) => void): NodeJS.Process
removeListener(event: 'unhandledRejection', listener: (reason: any) => void): NodeJS.Process
}

let _handler: ((err: Error) => Promise<void>) | undefined

const plugin: Plugin = {
load: client => {
const internalClient = client as InternalClient
if (!internalClient._config.autoDetectErrors || !internalClient._config.enabledErrorTypes.unhandledRejections) return
_handler = err => {
// if we are in an async context, use the client from that context
const ctx = internalClient._clientContext && internalClient._clientContext.getStore()
const c = ctx || internalClient

// Report unhandled promise rejections as handled if the user has configured it
const unhandled = !internalClient._config.reportUnhandledPromiseRejectionsAsHandled

const event = c.Event.create(err, false, {
severity: 'error',
unhandled,
severityReason: { type: 'unhandledPromiseRejection' }
}, 'unhandledRejection handler', 1)

return new Promise<void>(resolve => {
c._notify(event, () => {}, (e, event) => {
if (e) c._logger.error('Failed to send event to Bugsnag')
const clientConfig = c._config as Client['_config'] & NodeConfig
clientConfig.onUnhandledRejection(err, event, c._logger)
resolve()
})
})
}

// Prepend the listener if we can (Node 6+)
const nodeProcess = process as ProcessWithUnhandledRejection
if (nodeProcess.prependListener) {
nodeProcess.prependListener('unhandledRejection', _handler)
} else {
nodeProcess.on('unhandledRejection', _handler)
}
},
destroy: () => {
if (_handler) {
const nodeProcess = process as ProcessWithUnhandledRejection
nodeProcess.removeListener('unhandledRejection', _handler)
}
}
}

export default plugin
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Client, Event, schema } from '@bugsnag/core'
import plugin from '../'
import plugin from '../src/unhandled-rejection'

describe('plugin: node unhandled rejection handler', () => {
it('should listen to the process#unhandledRejection event', () => {
Expand All @@ -8,7 +8,9 @@ describe('plugin: node unhandled rejection handler', () => {
const after = process.listeners('unhandledRejection').length
expect(before < after).toBe(true)
expect(c).toBe(c)
plugin.destroy()
if (typeof plugin.destroy === 'function') {
plugin.destroy()
}
})

it('does not add a process#unhandledRejection listener if autoDetectErrors=false', () => {
Expand Down Expand Up @@ -40,7 +42,9 @@ describe('plugin: node unhandled rejection handler', () => {
expect(event._handledState.unhandled).toBe(true)
expect(event._handledState.severity).toBe('error')
expect(event._handledState.severityReason).toEqual({ type: 'unhandledPromiseRejection' })
plugin.destroy()
if (typeof plugin.destroy === 'function') {
plugin.destroy()
}
done()
},
plugins: [plugin]
Expand Down Expand Up @@ -69,7 +73,9 @@ describe('plugin: node unhandled rejection handler', () => {
expect(event._handledState.unhandled).toBe(false)
expect(event._handledState.severity).toBe('error')
expect(event._handledState.severityReason).toEqual({ type: 'unhandledPromiseRejection' })
plugin.destroy()
if (typeof plugin.destroy === 'function') {
plugin.destroy()
}
done()
},
plugins: [plugin]
Expand Down Expand Up @@ -98,7 +104,9 @@ describe('plugin: node unhandled rejection handler', () => {
expect(event._handledState.unhandled).toBe(true)
expect(event._handledState.severity).toBe('error')
expect(event._handledState.severityReason).toEqual({ type: 'unhandledPromiseRejection' })
plugin.destroy()
if (typeof plugin.destroy === 'function') {
plugin.destroy()
}
done()
},
plugins: [plugin]
Expand Down Expand Up @@ -150,7 +158,9 @@ describe('plugin: node unhandled rejection handler', () => {

expect(options.onUnhandledRejection).toHaveBeenCalledTimes(1)
} finally {
plugin.destroy()
if (typeof plugin.destroy === 'function') {
plugin.destroy()
}
}
})

Expand Down Expand Up @@ -199,7 +209,9 @@ describe('plugin: node unhandled rejection handler', () => {
expect(listenersAfter[1]).toBe(listener)
} finally {
process.removeListener('unhandledRejection', listener)
plugin.destroy()
if (typeof plugin.destroy === 'function') {
plugin.destroy()
}
}
})
})
8 changes: 8 additions & 0 deletions packages/plugin-node-unhandled-rejection/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {
"lib": ["es2017"],
"types": ["node"]
}
}
38 changes: 0 additions & 38 deletions packages/plugin-node-unhandled-rejection/unhandled-rejection.js

This file was deleted.

1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
"packages/plugin-strip-project-root",
"packages/plugin-interaction-breadcrumbs",
"packages/plugin-intercept",
"packages/plugin-node-unhandled-rejection",
"packages/plugin-node-in-project",
"packages/plugin-node-surrounding-code",
"packages/plugin-node-uncaught-exception",
Expand Down