diff --git a/packages/plugin-navigation-breadcrumbs/package.json b/packages/plugin-navigation-breadcrumbs/package.json index 29d32cf7e9..c99b879c8b 100644 --- a/packages/plugin-navigation-breadcrumbs/package.json +++ b/packages/plugin-navigation-breadcrumbs/package.json @@ -2,6 +2,14 @@ "name": "@bugsnag/plugin-navigation-breadcrumbs", "version": "8.1.1", "main": "navigation-breadcrumbs.js", + "types": "dist/types/navigation-breadcrumbs.d.ts", + "exports": { + ".": { + "types": "./dist/types/navigation-breadcrumbs.d.ts", + "default": "./dist/navigation-breadcrumbs.js", + "import": "./dist/navigation-breadcrumbs.mjs" + } + }, "description": "@bugsnag/js plugin to record browser navigation as breadcrumbs", "homepage": "https://www.bugsnag.com/", "repository": { @@ -14,6 +22,11 @@ "files": [ "*.js" ], + "scripts": { + "build": "npm run build:npm", + "build:npm": "rollup --config rollup.config.npm.mjs", + "clean": "rm -rf dist/*" + }, "author": "Bugsnag", "license": "MIT", "devDependencies": { diff --git a/packages/plugin-navigation-breadcrumbs/rollup.config.npm.mjs b/packages/plugin-navigation-breadcrumbs/rollup.config.npm.mjs new file mode 100644 index 0000000000..e74dd35c57 --- /dev/null +++ b/packages/plugin-navigation-breadcrumbs/rollup.config.npm.mjs @@ -0,0 +1,5 @@ +import createRollupConfig from '../../.rollup/index.mjs' + +export default createRollupConfig({ + input: 'src/navigation-breadcrumbs.ts' +}) diff --git a/packages/plugin-navigation-breadcrumbs/navigation-breadcrumbs.js b/packages/plugin-navigation-breadcrumbs/src/navigation-breadcrumbs.ts similarity index 59% rename from packages/plugin-navigation-breadcrumbs/navigation-breadcrumbs.js rename to packages/plugin-navigation-breadcrumbs/src/navigation-breadcrumbs.ts index 873215dd83..10559b8053 100644 --- a/packages/plugin-navigation-breadcrumbs/navigation-breadcrumbs.js +++ b/packages/plugin-navigation-breadcrumbs/src/navigation-breadcrumbs.ts @@ -1,14 +1,29 @@ +import { Client, Plugin } from 'packages/core/types' + +interface PluginClient extends Client { + _isBreadcrumbTypeEnabled: (type: string) => boolean +} + +type ExtendedHistory = History & { + replaceState: History['replaceState'] & { _restore?: () => void } + pushState: History['pushState'] & { _restore?: () => void } +} + +type ExtendedWindow = Window & { + history: ExtendedHistory +} + /* * Leaves breadcrumbs when navigation methods are called or events are emitted */ -module.exports = (win = window) => { - const plugin = { +export default (win = window): Plugin => { + const plugin: Plugin = { load: (client) => { if (!('addEventListener' in win)) return - if (!client._isBreadcrumbTypeEnabled('navigation')) return + if (!(client as PluginClient)._isBreadcrumbTypeEnabled('navigation')) return // returns a function that will drop a breadcrumb with a given name - const drop = name => () => client.leaveBreadcrumb(name, {}, 'navigation') + const drop = (name: string) => () => client.leaveBreadcrumb(name, {}, 'navigation') // simple drops – just names, no meta win.addEventListener('pagehide', drop('Page hidden'), true) @@ -27,15 +42,15 @@ module.exports = (win = window) => { }, true) // the only way to know about replaceState/pushState is to wrap them… >_< - if (win.history.pushState) wrapHistoryFn(client, win.history, 'pushState', win, true) - if (win.history.replaceState) wrapHistoryFn(client, win.history, 'replaceState', win) + if (typeof win.history.pushState === 'function') wrapHistoryFn(client, win.history, 'pushState', win, true) + if (typeof win.history.replaceState === 'function') wrapHistoryFn(client, win.history, 'replaceState', win) } } if (process.env.NODE_ENV !== 'production') { - plugin.destroy = (win = window) => { - win.history.replaceState._restore() - win.history.pushState._restore() + plugin.destroy = (win: ExtendedWindow = window) => { + if (win.history.replaceState._restore) win.history.replaceState._restore() + if (win.history.pushState._restore) win.history.pushState._restore() } } @@ -43,26 +58,27 @@ module.exports = (win = window) => { } if (process.env.NODE_ENV !== 'production') { - exports.destroy = (win = window) => { - win.history.replaceState._restore() - win.history.pushState._restore() + exports.destroy = (win: ExtendedWindow = window) => { + if (win.history.replaceState._restore) win.history.replaceState._restore() + if (win.history.pushState._restore) win.history.pushState._restore() } } // takes a full url like http://foo.com:1234/pages/01.html?yes=no#section-2 and returns // just the path and hash parts, e.g. /pages/01.html?yes=no#section-2 -const relativeLocation = (url, win) => { - const a = win.document.createElement('A') +const relativeLocation = (url: string, win: Window) => { + const a = win.document.createElement('a') a.href = url return `${a.pathname}${a.search}${a.hash}` } -const stateChangeToMetadata = (win, state, title, url) => { +const stateChangeToMetadata = (win: Window, state: string, title: string, url?: string | URL | null) => { const currentPath = relativeLocation(win.location.href, win) return { title, state, prevState: getCurrentState(win), to: url || currentPath, from: currentPath } } -const wrapHistoryFn = (client, target, fn, win, resetEventCount = false) => { +type HistoryMethods = 'pushState' | 'replaceState' +const wrapHistoryFn = (client: Client, target: ExtendedHistory, fn: HistoryMethods, win: Window, resetEventCount = false) => { const orig = target[fn] target[fn] = (state, title, url) => { client.leaveBreadcrumb(`History ${fn}`, stateChangeToMetadata(win, state, title, url), 'navigation') @@ -70,14 +86,14 @@ const wrapHistoryFn = (client, target, fn, win, resetEventCount = false) => { if (resetEventCount && typeof client.resetEventCount === 'function') client.resetEventCount() // Internet Explorer will convert `undefined` to a string when passed, causing an unintended redirect // to '/undefined'. therefore we only pass the url if it's not undefined. - orig.apply(target, [state, title].concat(url !== undefined ? url : [])) + orig.apply(target, [state, title].concat(url !== undefined ? url : []) as Parameters) } if (process.env.NODE_ENV !== 'production') { target[fn]._restore = () => { target[fn] = orig } } } -const getCurrentState = (win) => { +const getCurrentState = (win: Window) => { try { return win.history.state } catch (e) {} diff --git a/packages/plugin-navigation-breadcrumbs/test/navigation-breadcrumbs.test.ts b/packages/plugin-navigation-breadcrumbs/test/navigation-breadcrumbs.test.ts index 1b05070d53..0d2c34b9de 100644 --- a/packages/plugin-navigation-breadcrumbs/test/navigation-breadcrumbs.test.ts +++ b/packages/plugin-navigation-breadcrumbs/test/navigation-breadcrumbs.test.ts @@ -1,4 +1,4 @@ -import plugin from '../navigation-breadcrumbs' +import plugin from '../src/navigation-breadcrumbs' import Client from '@bugsnag/core/client' diff --git a/packages/plugin-navigation-breadcrumbs/tsconfig.json b/packages/plugin-navigation-breadcrumbs/tsconfig.json new file mode 100644 index 0000000000..a4cc3029ff --- /dev/null +++ b/packages/plugin-navigation-breadcrumbs/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "include": ["src/**/*.ts"], + "compilerOptions": { + "types": ["node"] + } +} + \ No newline at end of file