Skip to content

Commit

Permalink
Merge pull request #2247 from bugsnag/PLAT-12986-plugin-network-bread…
Browse files Browse the repository at this point in the history
…crumbs

Convert @bugsnag/plugin-network-breadcrumbs to TypeScript
  • Loading branch information
gingerbenw authored Nov 6, 2024
2 parents 3db887f + 740afc0 commit 4d6971c
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 37 deletions.
17 changes: 15 additions & 2 deletions packages/plugin-network-breadcrumbs/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
{
"name": "@bugsnag/plugin-network-breadcrumbs",
"version": "8.1.1",
"main": "network-breadcrumbs.js",
"main": "dist/network-breadcrumbs.js",
"types": "dist/types/network-breadcrumbs.d.ts",
"exports": {
".": {
"types": "./dist/types/network-breadcrumbs.d.ts",
"default": "./dist/network-breadcrumbs.js",
"import": "./dist/network-breadcrumbs.mjs"
}
},
"description": "@bugsnag/js plugin to record browser requests as breadcrumbs",
"homepage": "https://www.bugsnag.com/",
"repository": {
Expand All @@ -12,7 +20,7 @@
"access": "public"
},
"files": [
"*.js"
"dist"
],
"author": "Bugsnag",
"license": "MIT",
Expand All @@ -21,5 +29,10 @@
},
"peerDependencies": {
"@bugsnag/core": "^8.0.0"
},
"scripts": {
"build": "npm run build:npm",
"build:npm": "rollup --config rollup.config.npm.mjs",
"clean": "rm -rf dist/*"
}
}
6 changes: 6 additions & 0 deletions packages/plugin-network-breadcrumbs/rollup.config.npm.mjs
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/network-breadcrumbs.ts",
external: ["@bugsnag/core/lib/es-utils/includes"]
});
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
import { Client, Config, Logger, Plugin } from '@bugsnag/core'
import includes from '@bugsnag/core/lib/es-utils/includes'

const BREADCRUMB_TYPE = 'request'

const includes = require('@bugsnag/core/lib/es-utils/includes')
interface GlobalWithFetchAndXHR {
fetch: typeof fetch
XMLHttpRequest: typeof XMLHttpRequest
WeakMap: typeof WeakMap
}

interface InternalClient extends Client {
_logger: Logger
_config: Required<Config>
_isBreadcrumbTypeEnabled: (type: string) => boolean
}

type FetchArguments = Parameters<Window['fetch']>

/*
* Leaves breadcrumbs when network requests occur
*/
module.exports = (_ignoredUrls = [], win = window) => {
let restoreFunctions = []
const plugin = {
export default (_ignoredUrls = [], win: GlobalWithFetchAndXHR = window): Plugin => {
let restoreFunctions: Array<() => void> = []
const plugin: Plugin = {
load: client => {
if (!client._isBreadcrumbTypeEnabled('request')) return
const internalClient = client as InternalClient

if (!internalClient._isBreadcrumbTypeEnabled('request')) return

const ignoredUrls = [
client._config.endpoints.notify,
client._config.endpoints.sessions
internalClient._config.endpoints.notify,
internalClient._config.endpoints.sessions
].concat(_ignoredUrls)

monkeyPatchXMLHttpRequest()
Expand All @@ -27,12 +44,12 @@ module.exports = (_ignoredUrls = [], win = window) => {
const requestHandlers = new WeakMap()

const originalOpen = win.XMLHttpRequest.prototype.open
win.XMLHttpRequest.prototype.open = function open (method, url) {
win.XMLHttpRequest.prototype.open = function open (method: string, url: string | URL) {
// it's possible for `this` to be `undefined`, which is not a valid key for a WeakMap
if (this) {
trackedRequests.set(this, { method, url })
}
originalOpen.apply(this, arguments)
originalOpen.apply(this, arguments as unknown as Parameters<typeof originalOpen>)
}

const originalSend = win.XMLHttpRequest.prototype.send
Expand All @@ -59,7 +76,7 @@ module.exports = (_ignoredUrls = [], win = window) => {
}
}

originalSend.apply(this, arguments)
originalSend.apply(this, arguments as unknown as Parameters<typeof originalSend>)
}

if (process.env.NODE_ENV !== 'production') {
Expand All @@ -70,9 +87,9 @@ module.exports = (_ignoredUrls = [], win = window) => {
}
}

function handleXHRLoad (method, url, status, duration) {
function handleXHRLoad (method: string, url: string, status: number, duration: number) {
if (url === undefined) {
client._logger.warn('The request URL is no longer present on this XMLHttpRequest. A breadcrumb cannot be left for this request.')
internalClient._logger.warn('The request URL is no longer present on this XMLHttpRequest. A breadcrumb cannot be left for this request.')
return
}

Expand All @@ -90,15 +107,15 @@ module.exports = (_ignoredUrls = [], win = window) => {
}
if (status >= 400) {
// contacted server but got an error response
client.leaveBreadcrumb('XMLHttpRequest failed', metadata, BREADCRUMB_TYPE)
internalClient.leaveBreadcrumb('XMLHttpRequest failed', metadata, BREADCRUMB_TYPE)
} else {
client.leaveBreadcrumb('XMLHttpRequest succeeded', metadata, BREADCRUMB_TYPE)
internalClient.leaveBreadcrumb('XMLHttpRequest succeeded', metadata, BREADCRUMB_TYPE)
}
}

function handleXHRError (method, url, duration) {
function handleXHRError (method: string, url: string, duration: number) {
if (url === undefined) {
client._logger.warn('The request URL is no longer present on this XMLHttpRequest. A breadcrumb cannot be left for this request.')
internalClient._logger.warn('The request URL is no longer present on this XMLHttpRequest. A breadcrumb cannot be left for this request.')
return
}

Expand All @@ -108,7 +125,7 @@ module.exports = (_ignoredUrls = [], win = window) => {
}

// failed to contact server
client.leaveBreadcrumb('XMLHttpRequest error', {
internalClient.leaveBreadcrumb('XMLHttpRequest error', {
method: String(method),
url: String(url),
duration: duration
Expand All @@ -120,17 +137,18 @@ module.exports = (_ignoredUrls = [], win = window) => {
// only patch it if it exists and if it is not a polyfill (patching a polyfilled
// fetch() results in duplicate breadcrumbs for the same request because the
// implementation uses XMLHttpRequest which is also patched)
// @ts-expect-error polyfill is not defined in the Fetch API
if (!('fetch' in win) || win.fetch.polyfill) return

const oldFetch = win.fetch
win.fetch = function fetch () {
const urlOrRequest = arguments[0]
const options = arguments[1]
win.fetch = function fetch (...args: FetchArguments) {
const urlOrRequest = args[0]
const options = args[1]

let method
let url = null
let method: string | undefined
let url: string | URL | null = null

if (urlOrRequest && typeof urlOrRequest === 'object') {
if (urlOrRequest && typeof urlOrRequest === 'object' && 'url' in urlOrRequest) {
url = urlOrRequest.url
if (options && 'method' in options) {
method = options.method
Expand All @@ -152,13 +170,13 @@ module.exports = (_ignoredUrls = [], win = window) => {
const requestStart = new Date()

// pass through to native fetch
oldFetch(...arguments)
oldFetch(...args)
.then(response => {
handleFetchSuccess(response, method, url, getDuration(requestStart))
handleFetchSuccess(response, String(method), String(url), getDuration(requestStart))
resolve(response)
})
.catch(error => {
handleFetchError(method, url, getDuration(requestStart))
handleFetchError(String(method), String(url), getDuration(requestStart))
reject(error)
})
})
Expand All @@ -171,23 +189,23 @@ module.exports = (_ignoredUrls = [], win = window) => {
}
}

const handleFetchSuccess = (response, method, url, duration) => {
const handleFetchSuccess = (response: Response, method: string, url: string, duration: number) => {
const metadata = {
method: String(method),
method,
status: response.status,
url: String(url),
url,
duration: duration
}
if (response.status >= 400) {
// when the request comes back with a 4xx or 5xx status it does not reject the fetch promise,
client.leaveBreadcrumb('fetch() failed', metadata, BREADCRUMB_TYPE)
internalClient.leaveBreadcrumb('fetch() failed', metadata, BREADCRUMB_TYPE)
} else {
client.leaveBreadcrumb('fetch() succeeded', metadata, BREADCRUMB_TYPE)
internalClient.leaveBreadcrumb('fetch() succeeded', metadata, BREADCRUMB_TYPE)
}
}

const handleFetchError = (method, url, duration) => {
client.leaveBreadcrumb('fetch() error', { method: String(method), url: String(url), duration: duration }, BREADCRUMB_TYPE)
const handleFetchError = (method: string, url: string, duration: number) => {
internalClient.leaveBreadcrumb('fetch() error', { method, url, duration: duration }, BREADCRUMB_TYPE)
}
}
}
Expand All @@ -202,4 +220,4 @@ module.exports = (_ignoredUrls = [], win = window) => {
return plugin
}

const getDuration = (startTime) => startTime && new Date() - startTime
const getDuration = (startTime: Date): number => startTime && Number(new Date()) - Number(startTime)
7 changes: 7 additions & 0 deletions packages/plugin-network-breadcrumbs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.json",
"include": ["src/**/*.ts"],
"compilerOptions": {
"types": ["node"]
}
}
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
"packages/plugin-aws-lambda",
"packages/plugin-contextualize",
"packages/plugin-navigation-breadcrumbs",
"packages/plugin-network-breadcrumbs",
"packages/plugin-server-session",
"packages/plugin-react",
"packages/plugin-vue",
Expand Down

0 comments on commit 4d6971c

Please sign in to comment.