[
  {
    "path": ".editorconfig",
    "content": "[**.ts]\nindent_size = 2\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: Bug report\nabout: Create a report to help us improve Airbrake\ntitle: ''\nlabels: bug\nassignees: ''\n\n---\n\n# 🐞 bug report\n\n### Affected Package\n<!-- Can you identify one or more @airbrake/* packages as the source of the bug? -->\n<!-- ✍️edit: --> The issue is caused by package @airbrake/....\n\n\n### Is this a regression?\n\n<!-- Did this behavior use to work in the previous version? -->\n<!-- ✍️--> Yes, the previous version in which this bug was not present was: ....\n\n\n### Description\n\n<!-- ✍️--> A clear and concise description of the problem...\n\n\n## 🔬 Minimal Reproduction\n<!-- Please create and share minimal reproduction of the issue. -->\n<!-- A gist or sample app repo are both great. -->\n<!-- ✍️--> https://gist.github.com/...\n\n<!--\nShare the link to the repo along with step-by-step instructions to reproduce the problem, as well as expected and actual behavior.\n\nIssues that don't have enough info and can't be reproduced will be closed.\n-->\n\n## 🔥 Exception or Error\n<pre><code>\n<!-- If the issue is accompanied by an exception or an error, please share it below: -->\n<!-- ✍️-->\n\n</code></pre>\n\n\n## 🌍  Your Environment\n\n**@airbrake/\\* version:**\n<pre><code>\n<!-- Copy and paste your @airbrake/* package version below -->\n<!-- ✍️-->\n\n</code></pre>\n\n**Anything else relevant?**\n<!-- ✍️Is this a browser specific issue? If so, please specify the browser and version. -->\n\n<!-- ✍️Do any of these matter: build configuration (Webpack, Angular CLI, Parcel, Rollup, etc.), operating system, package manager, ...? If so, please mention it below. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: Feature request\nabout: Suggest a feature for Airbrake\ntitle: ''\nlabels: enhancement\nassignees: ''\n\n---\n\n# 🚀 feature request\n\n### Relevant Package\n<!-- Can you identify one or more @airbrake/* packages that are relevant for this feature request? -->\n<!-- ✍️edit: --> This feature request is for @airbrake/....\n\n\n### Description\n<!-- ✍️--> A clear and concise description of the problem or missing capability...\n\n\n### Describe the solution you'd like\n<!-- ✍️--> If you have a solution in mind, please describe it.\n\n\n### Describe alternatives you've considered\n<!-- ✍️--> Have you considered any alternative solutions or workarounds?\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\n\nupdates:\n- package-ecosystem: npm\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: \"19:00\"\n    timezone: US/Central\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non: [push]\n\npermissions:\n  contents: read\n\njobs:\n  lint_and_test:\n    runs-on: ubuntu-latest\n\n    strategy:\n      matrix:\n        node-version: [14.x, 16.x, 18.x]\n\n    steps:\n      - uses: actions/checkout@v3\n      - name: Use Node.js ${{ matrix.node-version }}\n        uses: actions/setup-node@v3\n        with:\n          node-version: ${{ matrix.node-version }}\n      - run: yarn install\n      - run: yarn lint\n      - run: yarn build\n      - run: yarn test\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\n.rpt2_cache\npackages/*/yarn.lock\npackages/*/package-lock.json\npackages/*/dist\npackages/*/esm\npackages/browser/umd\n.ackrc\n"
  },
  {
    "path": ".prettierrc.toml",
    "content": "singleQuote = true\ntrailingComma = \"es5\"\narrowParens = \"always\"\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Airbrake JS Changelog\n\n### master\n\n### [2.1.9] (March 6, 2025)\n\n#### browser\n\n- Added the `keysAllowlist` option, which is a counter-part to the\n  `keysBlocklist` option. It filters out all the data from the notice except the\n  specified keys\n  ([#1335](https://github.com/airbrake/airbrake-js/pull/1335))\n- Added support for error reporting of \"falsey\" errors such as `null`, `NaN`,\n  `undefined`, `false`, `\"\"`\n  ([#1345](https://github.com/airbrake/airbrake-js/pull/1345))\n- Added the `instrumentation.unhandledrejection` option, which enables/disables\n  the Airbrake handler for the `unhandledrejection` event\n  ([#1356](https://github.com/airbrake/airbrake-js/pull/1356))\n\n### [2.1.8] (December 6, 2022)\n\n#### browser\n\n- Fixed relative import issues with Yarn's Plug'n'Play feature\n  ([#1135](https://github.com/airbrake/airbrake-js/pull/1135))\n- Stop filtering the `context` field in the notice payload. This payload\n  contains service information and it should never be modified\n  ([#1325](https://github.com/airbrake/airbrake-js/pull/1325))\n- Bumped `cross-fetch` dependency to `^3.1.5` (fixes a Dependabot security\n  alert) ([#1322](https://github.com/airbrake/airbrake-js/issues/1322))\n\n### [2.1.7] (October 4, 2021)\n\n- [browser/node] Fixed incorrect `yarn.lock` references\n  ([#1132](https://github.com/airbrake/airbrake-js/pull/1132))\n\n### [2.1.6] (October 4, 2021)\n\n- [browser] Fixed not being able to attach a response type when sending a\n  performance breakdown\n  ([#1128](https://github.com/airbrake/airbrake-js/pull/1128))\n\n### [2.1.5] (June 2, 2021)\n\n- [node] Specify which versions of node are supported\n  ([#1038](https://github.com/airbrake/airbrake-js/pull/1038))\n\n### [2.1.4] (April 16, 2021)\n\n- [browser] Fixed `TypeError: undefined is not an object (evaluating 'e.searchParams.append')` occurring in old browsers that don't support\n  `Object.entries` (such as Internet Explorer)\n  ([#1001](https://github.com/airbrake/airbrake-js/pull/1001),\n  [#1002](https://github.com/airbrake/airbrake-js/pull/1002))\n\n### [2.1.3] (February 22, 2021)\n\n- [browser/node] Fixed missing library files in v2.1.2\n\n### [2.1.2] (February 22, 2021)\n\n- [browser] Started catching errors in promises that occur in `RemoteSettings`\n  ([#949](https://github.com/airbrake/airbrake-js/pull/949))\n\n### [2.1.1] (February 20, 2021)\n\n- [browser] Removed unwanted `debugger` statement in `base_notifier.js` in the\n  distribution package\n  ([#948](https://github.com/airbrake/airbrake-js/pull/948))\n\n### [2.1.0] (February 19, 2021)\n\n- [browser/node] Added the `queryStats` and the `queueStats` option. They\n  allow/forbid reporting of queries or queues, respectively\n  ([#945](https://github.com/airbrake/airbrake-js/pull/945))\n- [browser/node] Fixed `_ignoreNextWindowError` undefined error when wrapping\n  errors ([#944](https://github.com/airbrake/airbrake-js/pull/944))\n- [node] Fixed warnings on loading of `notifier.js` when using Webpack\n  ([#936](https://github.com/airbrake/airbrake-js/pull/936))\n\n### [2.0.0] (February 18, 2021)\n\n- [browser/node] Removed deprecated `ignoreWindowError` option\n  ([#929](https://github.com/airbrake/airbrake-js/pull/929))\n- [browser/node] Removed deprecated `keysBlacklist` option\n  ([#930](https://github.com/airbrake/airbrake-js/pull/930))\n- [browser/node] Introduced the `remoteConfigHost` option. This option\n  configures the host that the notifier fetch remote configuration from.\n  ([#940](https://github.com/airbrake/airbrake-js/pull/940))\n- [browser/node] Introduced the `apmHost` option. This option configures the\n  host that the notifier should send APM events to.\n  ([#940](https://github.com/airbrake/airbrake-js/pull/940))\n- [browser/node] Introduced the `errorNotifications` option. This options\n  configures ability to send errors\n  ([#940](https://github.com/airbrake/airbrake-js/pull/940))\n- [browser/node] Introduced the `remoteConfig` option. This option configures\n  the remote configuration feature\n  ([#940](https://github.com/airbrake/airbrake-js/pull/940))\n- [browser/node] Added support for the remote configuration feature\n  ([#940](https://github.com/airbrake/airbrake-js/pull/940))\n\n### [1.4.2] (December 22, 2020)\n\n#### Changed\n\n- [node] Conditionally initialize ScopeManager\n  ([#894](https://github.com/airbrake/airbrake-js/pull/894))\n- [browser] Add the ability to disable console tracking via instrumentation\n  ([#860](https://github.com/airbrake/airbrake-js/pull/860))\n\n### [1.4.1] (August 10, 2020)\n\n#### Changed\n\n- [browser] Unhandled rejection errors now include `unhandledRejection: true`\n  as part of their `context`\n  ([#795](https://github.com/airbrake/airbrake-js/pull/795))\n\n### [1.4.0] (July 22, 2020)\n\n#### Changed\n\n- [browser/node] `notify` now includes the `url` property on the returned\n  `INotice` object\n  ([#780](https://github.com/airbrake/airbrake-js/pull/780))\n\n### [1.3.0] (June 19, 2020)\n\n#### Changed\n\n- [browser/node] Deprecate `keysBlacklist` in favor of `keysBlocklist`\n\n### [1.2.0] (May 29, 2020)\n\n#### Added\n\n- [node] New method to filter performance metrics\n  ([#726](https://github.com/airbrake/airbrake-js/pull/726))\n\n### [1.1.3] (May 26, 2020)\n\n#### Changed\n\n- [browser/node] Remove onUnhandledrejection parameter type\n\n### [1.1.2] (May 5, 2020)\n\n#### Fixed\n\n- [browser] Add guard for window being undefined\n  ([#684](https://github.com/airbrake/airbrake-js/pull/684))\n- [node] Report URL using `req.originalUrl` instead of `req.path` in Express\n  apps ([#691](https://github.com/airbrake/airbrake-js/pull/691))\n\n### [1.1.1] (April 28, 2020)\n\n#### Fixed\n\n- [node] Express route stat reporting\n  ([#671](https://github.com/airbrake/airbrake-js/pull/671))\n\n### [1.1.0] (April 22, 2020)\n\n#### Changed\n\n- [browser/node] Build process updates. Bumping minor version for this. See\n  [#646](https://github.com/airbrake/airbrake-js/pull/646)\n- [browser/node] Documentation updates\n\n### [1.0.7] (April 8, 2020)\n\n#### Added\n\n- [node] New config option to disable performance stats\n\n#### Changed\n\n- [browser/node] Build config updates\n- [browser/node] Update dependencies\n- [browser/node] Documentation updates\n- [browser/node] Update linting config\n\n#### Fixed\n\n- [browser] Fix stacktrace test for node v10\n- [browser/node] Fix linting errors\n\n### [1.0.6] (November 18, 2019)\n\n### [1.0.4] (November 12, 2019)\n\n### [1.0.3] (November 7, 2019)\n\n### [1.0.2] (October 28, 2019)\n\n### [1.0.1] (October 28, 2019)\n\n### [1.0.0] (October 21, 2019)\n\n[1.0.0]: https://github.com/airbrake/airbrake-js/releases/tag/v1.0.0\n[1.0.1]: https://github.com/airbrake/airbrake-js/releases/tag/v1.0.1\n[1.0.2]: https://github.com/airbrake/airbrake-js/releases/tag/v1.0.2\n[1.0.3]: https://github.com/airbrake/airbrake-js/releases/tag/v1.0.3\n[1.0.4]: https://github.com/airbrake/airbrake-js/releases/tag/v1.0.4\n[1.0.6]: https://github.com/airbrake/airbrake-js/releases/tag/v1.0.6\n[1.0.7]: https://github.com/airbrake/airbrake-js/releases/tag/v1.0.7\n[1.1.0]: https://github.com/airbrake/airbrake-js/releases/tag/v1.1.0\n[1.1.1]: https://github.com/airbrake/airbrake-js/releases/tag/v1.1.1\n[1.1.2]: https://github.com/airbrake/airbrake-js/releases/tag/v1.1.2\n[1.1.3]: https://github.com/airbrake/airbrake-js/releases/tag/v1.1.3\n[1.2.0]: https://github.com/airbrake/airbrake-js/releases/tag/v1.2.0\n[1.3.0]: https://github.com/airbrake/airbrake-js/releases/tag/v1.3.0\n[1.4.0]: https://github.com/airbrake/airbrake-js/releases/tag/v1.4.0\n[1.4.1]: https://github.com/airbrake/airbrake-js/releases/tag/v1.4.1\n[1.4.2]: https://github.com/airbrake/airbrake-js/releases/tag/v1.4.2\n[2.0.0]: https://github.com/airbrake/airbrake-js/releases/tag/v2.0.0\n[2.1.0]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.0\n[2.1.1]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.1\n[2.1.2]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.2\n[2.1.3]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.3\n[2.1.4]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.4\n[2.1.5]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.5\n[2.1.6]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.6\n[2.1.7]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.7\n[2.1.8]: https://github.com/airbrake/airbrake-js/releases/tag/v2.1.8\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nBug fixes and improvements may be submitted in the form of pull requests.\n\n## Development Setup\n\nYou will need [Node.js](https://nodejs.org/download) version 10+ and\n[yarn](https://yarnpkg.com/en/docs/install).\n\n`airbrake-js` is a monorepo containing multiple packages.\n[Lerna](https://lerna.js.org/) and\n[yarn workspaces](https://yarnpkg.com/features/workspaces) are used to manage\nthem. To get started, you'll need to install the project dependencies and run\nthe build script:\n\n```sh\nyarn\nyarn build\n```\n\n## Building\n\nRun `yarn build` within a package directory to build that specific package, or\nrun it at the project root to build all packages at once. `yarn build` must be\nrun before testing or linting.\n\n## Testing\n\nRun `yarn test` within a package directory to run tests for that specific\npackage, or run it at the project root to run tests for all packages at once.\n\n## Linting\n\nRun `yarn lint` within a package directory to lint that specific package, or run\nit at the project root to lint all packages at once.\n"
  },
  {
    "path": "LICENSE.md",
    "content": "# MIT License\n\nCopyright © 2022 Airbrake Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://airbrake-github-assets.s3.amazonaws.com/brand/airbrake-full-logo.png\" width=\"200\">\n</p>\n\n# Official Airbrake Notifiers for JavaScript\n\n[![Build Status](https://github.com/airbrake/airbrake-js/workflows/CI/badge.svg?branch=master)](https://github.com/airbrake/airbrake-js/actions?query=branch%3Amaster)\n[![npm version](https://img.shields.io/npm/v/@airbrake/browser.svg)](https://www.npmjs.com/package/@airbrake/browser)\n\nPlease choose one of the following packages:\n\n[@airbrake/browser](packages/browser) for web browsers.\n\n[@airbrake/node](packages/node) for Node.js.\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"version\": \"2.1.9\",\n  \"npmClient\": \"yarn\",\n  \"useWorkspaces\": true\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"airbrake\",\n  \"private\": true,\n  \"devDependencies\": {\n    \"concurrently\": \"^7.6.0\",\n    \"lerna\": \"^6.0.3\",\n    \"prettier\": \"^2.0.2\",\n    \"tslint\": \"^6.1.0\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"tslint-plugin-prettier\": \"^2.3.0\",\n    \"typescript\": \"^4.0.2\"\n  },\n  \"scripts\": {\n    \"build\": \"lerna run build\",\n    \"build:watch\": \"lerna run build:watch --parallel\",\n    \"clean\": \"lerna run clean\",\n    \"lint\": \"lerna run lint\",\n    \"test\": \"lerna run test\"\n  },\n  \"workspaces\": [\n    \"packages/*\"\n  ]\n}\n"
  },
  {
    "path": "packages/browser/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Airbrake Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/browser/README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://airbrake-github-assets.s3.amazonaws.com/brand/airbrake-full-logo.png\" width=\"200\">\n</p>\n\n# Official Airbrake Notifier for Browsers\n\n[![Build Status](https://github.com/airbrake/airbrake-js/workflows/CI/badge.svg?branch=master)](https://github.com/airbrake/airbrake-js/actions?query=branch%3Amaster)\n[![npm version](https://img.shields.io/npm/v/@airbrake/browser.svg)](https://www.npmjs.com/package/@airbrake/browser)\n[![npm dm](https://img.shields.io/npm/dm/@airbrake/browser.svg)](https://www.npmjs.com/package/@airbrake/browser)\n[![npm dt](https://img.shields.io/npm/dt/@airbrake/browser.svg)](https://www.npmjs.com/package/@airbrake/browser)\n\nThe official Airbrake notifier for capturing JavaScript errors in web browsers\nand reporting them to [Airbrake](http://airbrake.io). If you're looking for\nNode.js support, there is a\n[separate package](https://github.com/airbrake/airbrake-js/tree/master/packages/node).\n\n## Installation\n\nUsing yarn:\n\n```sh\nyarn add @airbrake/browser\n```\n\nUsing npm:\n\n```sh\nnpm install @airbrake/browser\n```\n\nUsing a `<script>` tag via jsdelivr:\n\n```html\n<script src=\"https://cdn.jsdelivr.net/npm/@airbrake/browser\"></script>\n```\n\nUsing a `<script>` tag via unpkg:\n\n```html\n<script src=\"https://unpkg.com/@airbrake/browser\"></script>\n```\n\n## Basic Usage\n\nFirst, initialize the notifier with the project ID and project key taken from\n[Airbrake](https://airbrake.io). To find your `project_id` and `project_key`\nnavigate to your project's _Settings_ and copy the values from the right\nsidebar:\n\n![][project-idkey]\n\n```js\nimport { Notifier } from '@airbrake/browser';\n\nconst airbrake = new Notifier({\n  projectId: 1,\n  projectKey: 'REPLACE_ME',\n  environment: 'production',\n});\n```\n\nThen, you can send a textual message to Airbrake:\n\n```js\nlet promise = airbrake.notify(`user id=${user_id} not found`);\npromise.then((notice) => {\n  if (notice.id) {\n    console.log('notice id', notice.id);\n  } else {\n    console.log('notify failed', notice.error);\n  }\n});\n```\n\nor report errors directly:\n\n```js\ntry {\n  throw new Error('Hello from Airbrake!');\n} catch (err) {\n  airbrake.notify(err);\n}\n```\n\nAlternatively, you can wrap any code which may throw errors using the `wrap`\nmethod:\n\n```js\nlet startApp = () => {\n  throw new Error('Hello from Airbrake!');\n};\nstartApp = airbrake.wrap(startApp);\n\n// Any exceptions thrown in startApp will be reported to Airbrake.\nstartApp();\n```\n\nor use the `call` shortcut:\n\n```js\nlet startApp = () => {\n  throw new Error('Hello from Airbrake!');\n};\n\nairbrake.call(startApp);\n```\n\n## Example configurations\n\n- [AngularJS](examples/angularjs)\n- [Angular](examples/angular)\n- [Legacy](examples/legacy)\n- [Rails](examples/rails)\n- [React](examples/react)\n- [Redux](examples/redux)\n- [Vue.js](examples/vuejs)\n\n## Advanced Usage\n\n### Notice Annotations\n\nIt's possible to annotate error notices with all sorts of useful information at\nthe time they're captured by supplying it in the object being reported.\n\n```js\ntry {\n  startApp();\n} catch (err) {\n  airbrake.notify({\n    error: err,\n    context: { component: 'bootstrap' },\n    environment: { env1: 'value' },\n    params: { param1: 'value' },\n    session: { session1: 'value' },\n  });\n}\n```\n\n### Severity\n\n[Severity](https://airbrake.io/docs/airbrake-faq/what-is-severity/) allows\ncategorizing how severe an error is. By default, it's set to `error`. To\nredefine severity, simply overwrite `context/severity` of a notice object:\n\n```js\nairbrake.notify({\n  error: err,\n  context: { severity: 'warning' },\n});\n```\n\n### Filtering errors\n\nThere may be some errors thrown in your application that you're not interested\nin sending to Airbrake, such as errors thrown by 3rd-party libraries, or by\nbrowser extensions run by your users.\n\nThe Airbrake notifier makes it simple to ignore this chaff while still\nprocessing legitimate errors. Add filters to the notifier by providing filter\nfunctions to `addFilter`.\n\n`addFilter` accepts the entire\n[error notice](https://airbrake.io/docs/api/#create-notice-v3) to be sent to\nAirbrake and provides access to the `context`, `environment`, `params`,\nand `session` properties. It also includes the single-element `errors` array\nwith its `backtrace` property and associated backtrace lines.\n\nThe return value of the filter function determines whether or not the error\nnotice will be submitted.\n\n- If `null` is returned, the notice is ignored.\n- Otherwise, the returned notice will be submitted.\n\nAn error notice must pass all provided filters to be submitted.\n\nIn the following example all errors triggered by admins will be ignored:\n\n```js\nairbrake.addFilter((notice) => {\n  if (notice.params.admin) {\n    // Ignore errors from admin sessions.\n    return null;\n  }\n  return notice;\n});\n```\n\nFilters can be also used to modify notice payload, e.g. to set the environment\nand application version:\n\n```js\nairbrake.addFilter((notice) => {\n  notice.context.environment = 'production';\n  notice.context.version = '1.2.3';\n  return notice;\n});\n```\n\n### Filtering keys\n\n#### keysBlocklist\n\nWith the `keysBlocklist` option, you can specify a list of keys containing\nsensitive information that must be filtered out:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  keysBlocklist: [\n    'password', // exact match\n    /secret/, // regexp match\n  ],\n});\n```\n\n#### keysAllowlist\n\nWith the `keysAllowlist` option, you can specify a list of keys that should\n_not_ be filtered. All other keys will be substituted with the `[Filtered]`\nlabel.\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  keysAllowlist: [\n    'email', // exact match\n    /name/, // regexp match\n  ],\n});\n```\n\n### Source maps\n\nAirbrake supports using private and public source maps. Check out our docs for\nmore info:\n\n- [Private source maps](https://airbrake.io/docs/features/private-sourcemaps/)\n- [Public source maps](https://airbrake.io/docs/features/public-sourcemaps/)\n\n### Instrumentation\n\n#### console\n\n`@airbrake/browser` automatically instruments `console.log` function calls in\norder to collect logs and send them with the first error. You can disable that\nbehavior using the `instrumentation` option:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  instrumentation: {\n    console: false,\n  },\n});\n```\n\n#### fetch\n\nInstruments [`fetch`][fetch] calls and sends performance statistics to Airbrake.\nYou can disable that behavior using the `instrumentation` option:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  instrumentation: {\n    fetch: false,\n  },\n});\n```\n\n#### onerror\n\nReports the errors occurring in the Window's [error event][onerror]. You can\ndisable that behavior using the `instrumentation` option:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  instrumentation: {\n    onerror: false,\n  },\n});\n```\n\n#### history\n\nRecords the history of events that led to the error and sends it to Airbrake.\nYou can disable that behavior using the `instrumentation` option:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  instrumentation: {\n    history: false,\n  },\n});\n```\n\n#### xhr\n\nInstruments [XMLHttpRequest][xhr] requests and sends performance statistics to\nAirbrake. You can disable that behavior using the `instrumentation` option:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  instrumentation: {\n    xhr: false,\n  },\n});\n```\n\n#### unhandledrejection\n\nInstruments the [unhandledrejection][unhandledrejection] event and sends\nperformance statistics to Airbrake. You can disable that behavior using the\n`instrumentation` option:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  instrumentation: {\n    unhandledrejection: false,\n  },\n});\n```\n\n### APM\n\n#### Routes\n\n```js\nimport { Notifier } from '@airbrake/browser';\n\nconst airbrake = new Notifier({\n  projectId: 1,\n  projectKey: 'REPLACE_ME',\n  environment: 'production',\n});\n\nconst routeMetric = this.airbrake.routes.start(\n  'GET', // HTTP method name\n  '/abc', // Route name\n  200, // Status code\n  'application/json' // Content-Type header\n);\nthis.airbrake.routes.notify(routeMetric);\n```\n\n#### Queries\n\n```js\nimport { Notifier } from '@airbrake/browser';\n\nconst airbrake = new Notifier({\n  projectId: 1,\n  projectKey: 'REPLACE_ME',\n  environment: 'production',\n});\n\nconst queryInfo = this.airbrake.queries.start('SELECT * FROM things;');\nqueryInfo.file = 'file.js';\nqueryInfo.func = 'callerFunc';\nqueryInfo.line = 21;\nqueryInfo.method = 'GET';\nqueryInfo.route = '/abc';\n\nthis.airbrake.queries.notify(queryInfo);\n```\n\n#### Queues\n\n```js\nimport { Notifier } from '@airbrake/browser';\n\nconst airbrake = new Notifier({\n  projectId: 1,\n  projectKey: 'REPLACE_ME',\n  environment: 'production',\n});\n\nconst queueInfo = this.airbrake.queues.start('FooWorker');\nthis.airbrake.queues.notify(queueInfo);\n```\n\n[project-idkey]: https://s3.amazonaws.com/airbrake-github-assets/airbrake-js/project-id-key.png\n[fetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch\n[onerror]: https://developer.mozilla.org/en-US/docs/Web/API/Window/error_event\n[xhr]: https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest\n[unhandledrejection]: https://developer.mozilla.org/en-US/docs/Web/API/Window/unhandledrejection_event\n"
  },
  {
    "path": "packages/browser/babel.config.js",
    "content": "module.exports = {\n  presets: [['@babel/preset-env', { targets: { node: 'current' } }]],\n};\n"
  },
  {
    "path": "packages/browser/examples/angular/README.md",
    "content": "# Usage with Angular\n\n### Create an error handler\nThe first step is to create an error handler with a `Notifier`\ninitialized with your `projectId` and `projectKey`. In this example the\nhandler will be in a file called `airbrake-error-handler.ts`.\n\n```ts\n// src/app/airbrake-error-handler.ts\n\nimport { ErrorHandler } from '@angular/core';\nimport { Notifier } from '@airbrake/browser';\n\nexport class AirbrakeErrorHandler implements ErrorHandler {\n  airbrake: Notifier;\n\n  constructor() {\n    this.airbrake = new Notifier({\n      projectId: 1,\n      projectKey: 'FIXME',\n      environment: 'production'\n    });\n  }\n\n  handleError(error: any): void {\n    this.airbrake.notify(error);\n  }\n}\n```\n\n### Add the error handler to your `AppModule`\n\nThe last step is adding the `AirbrakeErrorHandler` to your `AppModule`, then\nyour app will be ready to report errors to Airbrake.\n\n```ts\n// src/app/app.module.ts\n\nimport { BrowserModule } from '@angular/platform-browser';\nimport { NgModule, ErrorHandler } from '@angular/core';\n\nimport { AppComponent } from './app.component';\nimport { AirbrakeErrorHandler } from './airbrake-error-handler';\n\n@NgModule({\n  declarations: [\n    AppComponent\n  ],\n  imports: [\n    BrowserModule\n  ],\n  providers: [{provide: ErrorHandler, useClass: AirbrakeErrorHandler}],\n  bootstrap: [AppComponent]\n})\nexport class AppModule { }\n```\n\nTo test that Airbrake has been installed correctly in your Angular project,\njust open up the JavaScript console in your internet browser and paste in:\n\n```js\nwindow.onerror(\"TestError: This is a test\", \"path/to/file.js\", 123);\n```\n"
  },
  {
    "path": "packages/browser/examples/angularjs/README.md",
    "content": "# Usage with AngularJS\n\nIntegration with AngularJS is as simple as adding an [$exceptionHandler][1]:\n\n```js\n// app.js\nconst module = angular.module('app', []);\n\nmodule.factory('$exceptionHandler', function ($log) {\n  const airbrake = new Airbrake.Notifier({\n    projectId: 1, // Airbrake project id\n    projectKey: 'FIXME', // Airbrake project API key\n  });\n\n  airbrake.addFilter(function (notice) {\n    notice.context.environment = 'production';\n    return notice;\n  });\n\n  return function (exception, cause) {\n    $log.error(exception);\n    airbrake.notify({ error: exception, params: { angular_cause: cause } });\n  };\n});\n\nmodule.controller('HelloWorldCtrl', function ($scope) {\n  throw new Error('Uh oh, something happened');\n\n  $scope.message = 'Hello World';\n});\n```\n\n```html\n<!-- index.html -->\n<!DOCTYPE html>\n<html ng-app=\"app\">\n  <head>\n    <meta charset=\"utf 8\" />\n    <title>Hello World</title>\n  </head>\n  <body>\n    <h1 ng-controller=\"HelloWorldCtrl\">{{message}}</h1>\n    <script src=\"https://code.angularjs.org/1.8.2/angular.js\"></script>\n    <script src=\"https://cdn.jsdelivr.net/npm/@airbrake/browser\"></script>\n    <script src=\"app.js\"></script>\n  </body>\n</html>\n```\n\n[1]: https://docs.angularjs.org/api/ng/service/$exceptionHandler\n"
  },
  {
    "path": "packages/browser/examples/extjs/README.md",
    "content": "# Usage with Ext JS\n\n### Install the `@airbrake/browser` package\n\n```sh\nnpm i @airbrake/browser\n```\n\n### Make the package available to the ExtJS framework\n\nInside the `index.js` file located at the root of your project, require the\npackage and set it as an Ext global property.\n\n```js\n// index.js\n\n// To avoid naming conflicts with existing ExtJS properties, prepend your\n// package name with x\n\n// https://docs.sencha.com/extjs/7.4.0/guides/using_systems/using_npm/adding_npm_packages.html\nExt.xAirbrake = require('@airbrake/browser');\n```\n\n### Instantiate the notifier\n\nAlso in `index.js`, create a new notifier instance with your `projectId` and\n`projectKey`.\n\n```js\nnew Ext.xAirbrake.Notifier({\n  projectId: 1,\n  projectKey: 'FIXME'\n});\n```\n\nAirbrake will now automatically report any unhandled exceptions. If you want to\nsend any errors manually, set the notifier instance to a variable and call\n`.notify()` where needed.\n"
  },
  {
    "path": "packages/browser/examples/legacy/README.md",
    "content": "# Usage with legacy applications\n\nThis example loads @airbrake/browser using a `script` tag via the jsdelivr CDN.\nOpen `index.html` in your browser to start it.\n"
  },
  {
    "path": "packages/browser/examples/legacy/app.js",
    "content": "var airbrake = new Airbrake.Notifier({\n  projectId: 1,\n  projectKey: 'FIXME',\n});\n\n$(function() {\n  $('#send_error').click(function() {\n    try {\n      history.pushState({ foo: 'bar' }, 'Send error', 'send-error');\n    } catch (_) {}\n\n    var val = $('#error_text').val();\n    throw new Error(val);\n  });\n});\n\ntry {\n  throw new Error('Hello from Airbrake!');\n} catch (err) {\n  airbrake.notify(err).then(function(notice) {\n    if (notice.id) {\n      console.log('notice id:', notice.id);\n    } else {\n      console.log('notify failed:', notice.error);\n    }\n  });\n}\n\nthrow new Error('uncaught error');\n"
  },
  {
    "path": "packages/browser/examples/legacy/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <title>Airbrake legacy example</title>\n\n  <script src=\"https://cdn.jsdelivr.net/npm/@airbrake/browser\"></script>\n  <script src=\"https://code.jquery.com/jquery-3.1.1.min.js\"></script>\n\n  <script src=\"app.js\"></script>\n</head>\n<body>\n  <input id=\"error_text\" />\n  <button id=\"send_error\">Send error to Airbrake</button>\n</body>\n</html>\n"
  },
  {
    "path": "packages/browser/examples/nextjs/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n.env*\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n.env.local\n"
  },
  {
    "path": "packages/browser/examples/nextjs/README.md",
    "content": "# Usage with Next.js\nThis is a sample application that can be found at\n[Learn Next.js](https://nextjs.org/learn). It has been adapted to include\nclient-side and server-side error reporting with Airbrake.\n\nTo run the app, run `npm install` then `npm run dev`. The app will be available\nat [http://localhost:3000](http://localhost:3000). Sample client-side\nerrors are triggered with a `Throw error` button on the [homepage](http://localhost:3000)\n(`pages/index.js`). Sample server-side errors are triggered by visiting one of\nthe [blog post pages](http://localhost:3000/posts/ssg-ssr) (`posts/[id].js`).\n\n## Client-side error reporting\nTo report client-side errors from a Next.js app, you'll need to set up and use an\n[`ErrorBoundary` component](https://reactjs.org/docs/error-boundaries.html),\nand initialize a `Notifier` with your `projectId` and `projectKey`.\n\n```js\nimport React from 'react';\nimport { Notifier } from '@airbrake/browser';\n\nclass ErrorBoundary extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = { hasError: false };\n    this.airbrake = new Notifier({\n      projectId: 1,\n      projectKey: 'FIXME'\n    });\n  }\n\n  componentDidCatch(error, info) {\n    // Display fallback UI\n    this.setState({ hasError: true });\n    // Send error to Airbrake\n    this.airbrake.notify({\n      error: error,\n      params: {info: info}\n    });\n  }\n\n  render() {\n    if (this.state.hasError) {\n      // You can render any custom fallback UI\n      return <h1>Something went wrong.</h1>;\n    }\n    return this.props.children;\n  }\n}\n\nexport default ErrorBoundary;\n```\n\nThen, you can use it as a regular component:\n\n```html\n<ErrorBoundary>\n  <MyWidget />\n</ErrorBoundary>\n```\n\n## Server-side error reporting\nTo report server-side errors from a Next.js app, you'll need to [override the\ndefault `Error` component](https://nextjs.org/docs/advanced-features/custom-error-page#more-advanced-error-page-customizing).\nDefine the file `pages/_error.js` and add the following code:\n\n```js\nfunction Error({ statusCode }) {\n  return (\n    <p>\n      {statusCode\n        ? `An error ${statusCode} occurred on server`\n        : 'An error occurred on client'}\n    </p>\n  )\n}\n\nError.getInitialProps = ({ res, err }) => {\n  if (typeof window === \"undefined\") {\n    const Airbrake = require('@airbrake/node')\n    const airbrake = new Airbrake.Notifier({\n      projectId: 1,\n      projectKey: 'FIXME'\n    });\n    if (err) {\n      airbrake.notify(err)\n    }\n  }\n\n  const statusCode = res ? res.statusCode : err ? err.statusCode : 404\n  return { statusCode }\n}\n\nexport default Error\n```\n"
  },
  {
    "path": "packages/browser/examples/nextjs/components/date.js",
    "content": "import { parseISO, format } from 'date-fns'\n\nexport default function Date({ dateString }) {\n  const date = parseISO(dateString)\n  return <time dateTime={dateString}>{format(date, 'LLLL d, yyyy')}</time>\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/components/error_boundary.js",
    "content": "import React from 'react';\nimport { Notifier } from '@airbrake/browser';\n\nclass ErrorBoundary extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = { hasError: false };\n    this.airbrake = new Notifier({\n      projectId: process.env.NEXT_PUBLIC_AIRBRAKE_PROJECT_ID,\n      projectKey: process.env.NEXT_PUBLIC_AIRBRAKE_PROJECT_KEY,\n      environment: process.env.NEXT_PUBLIC_ENV\n    });\n  }\n\n  componentDidCatch(error, info) {\n    // Display fallback UI\n    this.setState({ hasError: true });\n    // Send error to Airbrake\n    this.airbrake.notify({\n      error: error,\n      params: {info: info}\n    });\n  }\n\n  render() {\n    if (this.state.hasError) {\n      // You can render any custom fallback UI\n      return <h1>Something went wrong.</h1>;\n    }\n    return this.props.children;\n  }\n}\n\nexport default ErrorBoundary;\n"
  },
  {
    "path": "packages/browser/examples/nextjs/components/layout.js",
    "content": "import Head from 'next/head'\nimport Image from 'next/image'\nimport styles from './layout.module.css'\nimport utilStyles from '../styles/utils.module.css'\nimport Link from 'next/link'\n\nconst name = 'Shu Uesugi'\nexport const siteTitle = 'Next.js Sample Website'\n\nexport default function Layout({ children, home }) {\n  return (\n    <div className={styles.container}>\n      <Head>\n        <link rel=\"icon\" href=\"/favicon.ico\" />\n        <meta\n          name=\"description\"\n          content=\"Learn how to build a personal website using Next.js\"\n        />\n        <meta\n          property=\"og:image\"\n          content={`https://og-image.vercel.app/${encodeURI(\n            siteTitle\n          )}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.zeit.co%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}\n        />\n        <meta name=\"og:title\" content={siteTitle} />\n        <meta name=\"twitter:card\" content=\"summary_large_image\" />\n      </Head>\n      <header className={styles.header}>\n        {home ? (\n          <>\n            <Image\n              priority\n              src=\"/images/profile.jpg\"\n              className={utilStyles.borderCircle}\n              height={144}\n              width={144}\n              alt={name}\n            />\n            <h1 className={utilStyles.heading2Xl}>{name}</h1>\n          </>\n        ) : (\n          <>\n            <Link href=\"/\">\n              <a>\n                <Image\n                  priority\n                  src=\"/images/profile.jpg\"\n                  className={utilStyles.borderCircle}\n                  height={108}\n                  width={108}\n                  alt={name}\n                />\n              </a>\n            </Link>\n            <h2 className={utilStyles.headingLg}>\n              <Link href=\"/\">\n                <a className={utilStyles.colorInherit}>{name}</a>\n              </Link>\n            </h2>\n          </>\n        )}\n      </header>\n      <main>{children}</main>\n      {!home && (\n        <div className={styles.backToHome}>\n          <Link href=\"/\">\n            <a>← Back to home</a>\n          </Link>\n        </div>\n      )}\n    </div>\n  )\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/components/layout.module.css",
    "content": ".container {\n  max-width: 36rem;\n  padding: 0 1rem;\n  margin: 3rem auto 6rem;\n}\n\n.header {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n}\n\n.backToHome {\n  margin: 3rem 0 0;\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/lib/posts.js",
    "content": "import fs from 'fs'\nimport path from 'path'\nimport matter from 'gray-matter'\nimport { remark } from 'remark'\nimport html from 'remark-html'\n\nconst postsDirectory = path.join(process.cwd(), 'posts')\n\nexport function getSortedPostsData() {\n  // Get file names under /posts\n  const fileNames = fs.readdirSync(postsDirectory)\n  const allPostsData = fileNames.map(fileName => {\n    // Remove \".md\" from file name to get id\n    const id = fileName.replace(/\\.md$/, '')\n\n    // Read markdown file as string\n    const fullPath = path.join(postsDirectory, fileName)\n    const fileContents = fs.readFileSync(fullPath, 'utf8')\n\n    // Use gray-matter to parse the post metadata section\n    const matterResult = matter(fileContents)\n\n    // Combine the data with the id\n    return {\n      id,\n      ...matterResult.data\n    }\n  })\n  // Sort posts by date\n  return allPostsData.sort((a, b) => {\n    if (a.date < b.date) {\n      return 1\n    } else {\n      return -1\n    }\n  })\n}\n\nexport function getAllPostIds() {\n  const fileNames = fs.readdirSync(postsDirectory)\n  return fileNames.map(fileName => {\n    return {\n      params: {\n        id: fileName.replace(/\\.md$/, '')\n      }\n    }\n  })\n}\n\nexport async function getPostData(id) {\n  const fullPath = path.join(postsDirectory, `${id}.md`)\n  const fileContents = fs.readFileSync(fullPath, 'utf8')\n\n  // Use gray-matter to parse the post metadata section\n  const matterResult = matter(fileContents)\n\n  // Use remark to convert markdown into HTML string\n  const processedContent = await remark()\n    .use(html)\n    .process(matterResult.content)\n  const contentHtml = processedContent.toString()\n\n  // Combine the data with the id and contentHtml\n  return {\n    id,\n    contentHtml,\n    ...matterResult.data\n  }\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n  },\n  \"dependencies\": {\n    \"@airbrake/browser\": \"^2.1.7\",\n    \"@airbrake/node\": \"^2.1.7\",\n    \"date-fns\": \"^2.11.1\",\n    \"gray-matter\": \"^4.0.2\",\n    \"next\": \"latest\",\n    \"react\": \"17.0.2\",\n    \"react-dom\": \"17.0.2\",\n    \"remark\": \"^14.0.1\",\n    \"remark-html\": \"^15.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/pages/_app.js",
    "content": "import '../styles/global.css'\n\nexport default function App({ Component, pageProps }) {\n  return <Component {...pageProps} />\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/pages/_error.js",
    "content": "function Error({ statusCode }) {\n  return (\n    <p>\n      {statusCode\n        ? `An error ${statusCode} occurred on server`\n        : 'An error occurred on client'}\n    </p>\n  )\n}\n\nError.getInitialProps = ({ res, err }) => {\n  if (typeof window === \"undefined\") {\n    const Airbrake = require('@airbrake/node')\n    const airbrake = new Airbrake.Notifier({\n      projectId: process.env.NEXT_PUBLIC_AIRBRAKE_PROJECT_ID,\n      projectKey: process.env.NEXT_PUBLIC_AIRBRAKE_PROJECT_KEY,\n    });\n    if (err) {\n      airbrake.notify(err)\n    }\n  }\n\n  const statusCode = res ? res.statusCode : err ? err.statusCode : 404\n  return { statusCode }\n}\n\nexport default Error\n"
  },
  {
    "path": "packages/browser/examples/nextjs/pages/api/hello.js",
    "content": "export default (req, res) => {\n  res.status(200).json({ text: 'Hello' })\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/pages/index.js",
    "content": "import Head from 'next/head'\nimport Layout, { siteTitle } from '../components/layout'\nimport utilStyles from '../styles/utils.module.css'\nimport { getSortedPostsData } from '../lib/posts'\nimport Link from 'next/link'\nimport Date from '../components/date'\nimport ErrorBoundary from '../components/error_boundary'\n\nexport default function Home({ allPostsData }) {\n  return (\n    <ErrorBoundary>\n      <Layout home>\n        <Head>\n          <title>{siteTitle}</title>\n        </Head>\n        <section className={utilStyles.headingMd}>\n          <p>\n            Hello, I’m <strong>Shu</strong>. I’m a software engineer and a\n            translator (English/Japanese). You can contact me on{' '}\n            <a href=\"https://twitter.com/chibicode\">Twitter</a>.\n          </p>\n          <p>\n            (This is a sample website - you’ll be building a site like this in{' '}\n            <a href=\"https://nextjs.org/learn\">our Next.js tutorial</a>.)\n          </p>\n        </section>\n        <section className={`${utilStyles.headingMd} ${utilStyles.padding1px}`}>\n          <h2 className={utilStyles.headingLg}>Blog</h2>\n          <ul className={utilStyles.list}>\n            {allPostsData.map(({ id, date, title }) => (\n              <li className={utilStyles.listItem} key={id}>\n                <Link href={`/posts/${id}`}>\n                  <a>{title}</a>\n                </Link>\n                <br />\n                <small className={utilStyles.lightText}>\n                  <Date dateString={date} />\n                </small>\n              </li>\n            ))}\n          </ul>\n        </section>\n        <button\n          type=\"button\"\n          onClick={() => {\n            throw new Error(\"Client Error\");\n          }}\n        >\n          Throw error\n        </button>\n      </Layout>\n    </ErrorBoundary>\n  )\n}\n\nexport async function getStaticProps() {\n  const allPostsData = getSortedPostsData()\n  return {\n    props: {\n      allPostsData\n    }\n  }\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/pages/posts/[id].js",
    "content": "import Layout from '../../components/layout'\nimport { getAllPostIds, getPostData } from '../../lib/posts'\nimport Head from 'next/head'\nimport Date from '../../components/date'\nimport utilStyles from '../../styles/utils.module.css'\n\nexport default function Post({ postData }) {\n  return (\n    <Layout>\n      <Head>\n        <title>{postData.title}</title>\n      </Head>\n      <article>\n        <h1 className={utilStyles.headingXl}>{postData.title}</h1>\n        <div className={utilStyles.lightText}>\n          <Date dateString={postData.date} />\n        </div>\n        <div dangerouslySetInnerHTML={{ __html: postData.contentHtml }} />\n      </article>\n    </Layout>\n  )\n}\n\nexport async function getStaticPaths() {\n  throw new Error(\"Server Error\");\n  const paths = getAllPostIds()\n  return {\n    paths,\n    fallback: false\n  }\n}\n\nexport async function getStaticProps({ params }) {\n  const postData = await getPostData(params.id)\n  return {\n    props: {\n      postData\n    }\n  }\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/posts/pre-rendering.md",
    "content": "---\ntitle: \"Two Forms of Pre-rendering\"\ndate: \"2020-01-01\"\n---\n\nNext.js has two forms of pre-rendering: **Static Generation** and **Server-side Rendering**. The difference is in **when** it generates the HTML for a page.\n\n- **Static Generation** is the pre-rendering method that generates the HTML at **build time**. The pre-rendered HTML is then _reused_ on each request.\n- **Server-side Rendering** is the pre-rendering method that generates the HTML on **each request**.\n\nImportantly, Next.js lets you **choose** which pre-rendering form to use for each page. You can create a \"hybrid\" Next.js app by using Static Generation for most pages and using Server-side Rendering for others."
  },
  {
    "path": "packages/browser/examples/nextjs/posts/ssg-ssr.md",
    "content": "---\ntitle: \"When to Use Static Generation v.s. Server-side Rendering\"\ndate: \"2020-01-02\"\n---\n\nWe recommend using **Static Generation** (with and without data) whenever possible because your page can be built once and served by CDN, which makes it much faster than having a server render the page on every request.\n\nYou can use Static Generation for many types of pages, including:\n\n- Marketing pages\n- Blog posts\n- E-commerce product listings\n- Help and documentation\n\nYou should ask yourself: \"Can I pre-render this page **ahead** of a user's request?\" If the answer is yes, then you should choose Static Generation.\n\nOn the other hand, Static Generation is **not** a good idea if you cannot pre-render a page ahead of a user's request. Maybe your page shows frequently updated data, and the page content changes on every request.\n\nIn that case, you can use **Server-Side Rendering**. It will be slower, but the pre-rendered page will always be up-to-date. Or you can skip pre-rendering and use client-side JavaScript to populate data."
  },
  {
    "path": "packages/browser/examples/nextjs/styles/global.css",
    "content": "html,\nbody {\n  padding: 0;\n  margin: 0;\n  font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,\n    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;\n  line-height: 1.6;\n  font-size: 18px;\n}\n\n* {\n  box-sizing: border-box;\n}\n\na {\n  color: #0070f3;\n  text-decoration: none;\n}\n\na:hover {\n  text-decoration: underline;\n}\n\nimg {\n  max-width: 100%;\n  display: block;\n}\n"
  },
  {
    "path": "packages/browser/examples/nextjs/styles/utils.module.css",
    "content": ".heading2Xl {\n  font-size: 2.5rem;\n  line-height: 1.2;\n  font-weight: 800;\n  letter-spacing: -0.05rem;\n  margin: 1rem 0;\n}\n\n.headingXl {\n  font-size: 2rem;\n  line-height: 1.3;\n  font-weight: 800;\n  letter-spacing: -0.05rem;\n  margin: 1rem 0;\n}\n\n.headingLg {\n  font-size: 1.5rem;\n  line-height: 1.4;\n  margin: 1rem 0;\n}\n\n.headingMd {\n  font-size: 1.2rem;\n  line-height: 1.5;\n}\n\n.borderCircle {\n  border-radius: 9999px;\n}\n\n.colorInherit {\n  color: inherit;\n}\n\n.padding1px {\n  padding-top: 1px;\n}\n\n.list {\n  list-style: none;\n  padding: 0;\n  margin: 0;\n}\n\n.listItem {\n  margin: 0 0 1.25rem;\n}\n\n.lightText {\n  color: #666;\n}\n"
  },
  {
    "path": "packages/browser/examples/rails/README.md",
    "content": "# Usage with Ruby on Rails\n\n#### Option 1 - Asset pipeline\n\nCopy the latest compiled UMD package bundle from\n[https://unpkg.com/@airbrake/browser](https://unpkg.com/@airbrake/browser) to\n`vendor/assets/javascripts/airbrake.js` in your project.\n\nThen, add the following code to your Sprockets manifest:\n\n```javascript\n//= require airbrake\n\nvar airbrake = new Airbrake.Notifier({\n  projectId: 1,\n  projectKey: 'FIXME'\n});\n\nairbrake.addFilter(function(notice) {\n  notice.context.environment = \"<%= Rails.env %>\";\n  return notice;\n});\n\ntry {\n  throw new Error('Hello from Airbrake!');\n} catch (err) {\n  airbrake.notify(err).then(function(notice) {\n    if (notice.id) {\n      console.log('notice id:', notice.id);\n    } else {\n      console.log('notify failed:', notice.error);\n    }\n  });\n}\n```\n\n#### Option 2 - Webpacker\n\nAdd `@airbrake/broswer` to your application.\n\n```sh\nyarn add @airbrake/browser\n```\n\nIn your main application pack, import `@airbrake/browser` and configure the client.\n\n```js\nimport { Notifier } from '@airbrake/browser';\n\nconst airbrake = new Notifier({\n  projectId: 1,\n  projectKey: 'FIXME'\n});\n\nairbrake.addFilter((notice) => {\n  notice.context.environment = process.env.RAILS_ENV;\n  return notice;\n});\n\ntry {\n  throw new Error('Hello from Airbrake!');\n} catch (err) {\n  airbrake.notify(err).then((notice) => {\n    if (notice.id) {\n      console.log('notice id:', notice.id);\n    } else {\n      console.log('notify failed:', notice.error);\n    }\n  });\n}\n```\n\nYou should now be able to capture JavaScript exceptions in your Ruby on Rails\napplication.\n"
  },
  {
    "path": "packages/browser/examples/react/README.md",
    "content": "# Usage with React\n\nTo report errors from a React app, you'll need to set up and use an\n[`ErrorBoundary` component](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html)\nand initialize a `Notifier` with your `projectId` and `projectKey`.\n\n```js\nimport React from 'react';\nimport { Notifier } from '@airbrake/browser';\n\nclass ErrorBoundary extends React.Component {\n  constructor(props) {\n    super(props);\n    this.state = { hasError: false };\n    this.airbrake = new Notifier({\n      projectId: 1,\n      projectKey: 'FIXME'\n    });\n  }\n\n  componentDidCatch(error, info) {\n    // Display fallback UI\n    this.setState({ hasError: true });\n    // Send error to Airbrake\n    this.airbrake.notify({\n      error: error,\n      params: {info: info}\n    });\n  }\n\n  render() {\n    if (this.state.hasError) {\n      // You can render any custom fallback UI\n      return <h1>Something went wrong.</h1>;\n    }\n    return this.props.children;\n  }\n}\n\nexport default ErrorBoundary;\n```\n\nThen you can use it as a regular component:\n\n```html\n<ErrorBoundary>\n  <MyWidget />\n</ErrorBoundary>\n```\n\nRead [Error Handling in React 16](https://reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) for more details.\n"
  },
  {
    "path": "packages/browser/examples/redux/README.md",
    "content": "# Usage with Redux\n\n#### 1. Add dependencies\n```bash\nnpm install @airbrake/browser redux-airbrake --save\n```\n\n#### 2. Import dependency\n```js\nimport { Notifier } from '@airbrake/browser';\nimport airbrakeMiddleware from 'redux-airbrake';\n```\n\n#### 3. Configure & add middleware\n```js\nconst airbrake = new Notifier({\n  projectId: '******',\n  projectKey: '**************'\n});\n\nconst errorMiddleware = airbrakeMiddleware(airbrake);\n\nexport const store = createStore(\n  rootReducer,\n  applyMiddleware(\n    errorMiddleware\n  )\n);\n\nexport default store;\n```\n\n#### Adding notice annotations (optional)\n\nIt's possible to annotate error notices with all sorts of useful information at the time they're captured by supplying it in the object being reported.\n\n```js\nconst errorMiddleware = airbrakeMiddleware(airbrake, {\n  noticeAnnotations: { context: { environment: window.ENV } }\n});\n```\n\n#### Adding filters\n\nSince an Airbrake instrace is passed to the middleware, you can simply add\nfilters to the instance as described here:\n\n[https://github.com/airbrake/airbrake-js/tree/master/packages/browser#filtering-errors](https://github.com/airbrake/airbrake-js/tree/master/packages/browser#filtering-errors)\n\nFor full documentation, visit [redux-airbrake](https://github.com/alexcastillo/redux-airbrake) on GitHub.\n"
  },
  {
    "path": "packages/browser/examples/svelte/README.md",
    "content": "# Usage with Svelte\n\nIntegration with Svelte is as simple as adding `handleError` hooks (Server or Client):\n\n- For server error handling, add `handleError` of `HandleServerError` type\n- For handle error of client add `handleError` of `HandleClientError` type\n\n## App configuration\n\n```ts\n// app.d.ts\n\n// Add interface of Error\n\n// See https://kit.svelte.dev/docs/types#app\n// for information about these interfaces\n// and what to do when importing types\ndeclare global {\n  namespace App {\n    interface Error {\n      message: unknown;\n      errorId: string;\n    }\n  }\n}\n\nexport {};\n```\n\n## Server Hook\n\nTo establish an error handler for the server, use a `Notifier` with your `projectId` and `projectKey` as parameters. In this case, the handler will be located in the file `src/hooks.server.js`.\n\n```js\n// src/hooks.server.js\nimport crypto from 'crypto';\nimport { Notifier } from '@airbrake/browser';\n\nvar airbrake = new Notifier({\n  projectId: 1, // Airbrake project id\n  projectKey: 'FIXME', // Airbrake project API key\n});\n\nairbrake.addFilter(function (notice) {\n  notice.context.hooks = 'server';\n  return notice;\n});\n\n/** @type {import('@sveltejs/kit').HandleServerError} */\nexport function handleError({ error, event }) {\n  const errorId = crypto.randomUUID();\n  // example integration with https://airbrake.io/\n  airbrake.notify({\n    error: error,\n    params: { errorId: errorId, event: event },\n  });\n\n  return {\n    message: error,\n    errorId,\n  };\n}\n```\n\n## Client Hook\n\nTo establish an error handler for the client, use a `Notifier` with your `projectId` and `projectKey` as parameters. In this case, the handler will be located in the file `src/hooks.client.js`.\n\n```js\n// src/hooks.client.js\nimport crypto from 'crypto';\nimport { Notifier } from '@airbrake/browser';\n\nvar airbrake = new Notifier({\n  projectId: 1, // Airbrake project id\n  projectKey: 'FIXME', // Airbrake project API key\n});\n\nairbrake.addFilter(function (notice) {\n  notice.context.hooks = 'client';\n  return notice;\n});\n\n/** @type {import('@sveltejs/kit').HandleClientError} */\nexport function handleError({ error, event }) {\n  const errorId = crypto.randomUUID();\n  // example integration with https://airbrake.io/\n  airbrake.notify({\n    error: error,\n    params: { errorId: errorId, event: event },\n  });\n\n  return {\n    message: error,\n    errorId,\n  };\n}\n```\n\n## Test\n\nTo test that server hook has been installed correctly in your Svelte project.\n\n```js\n// +page.server.js\n\nimport { error } from '@sveltejs/kit';\nimport * as db from '$lib/server/database';\n\n/** @type {import('./$types').PageServerLoad} */\nexport async function load({ params }) {\n  const post = await db.getPost(params.slug);\n\n  if (!post) {\n    throw error(404, {\n      message: 'Not found',\n    });\n  }\n\n  return { post };\n}\n```\n\nTo test that client hook has been installed correctly in your Svelte project, just open up the JavaScript console in your internet browser and paste in:\n\n```js\nwindow.onerror('TestError: This is a test', 'path/to/file.js', 123);\n```\n"
  },
  {
    "path": "packages/browser/examples/vuejs/README.md",
    "content": "Usage with Vue.js\n==================\n\n### Vue 2\n\nYou can start reporting errors from your Vue 2 app by configuring an\n[`errorHandler`](https://vuejs.org/v2/api/#errorHandler) that uses a `Notifier`\ninitialized with your `projectId` and `projectKey`.\n\n```js\nimport { Notifier } from '@airbrake/browser'\n\nvar airbrake = new Notifier({\n  projectId: 1,\n  projectKey: 'FIXME'\n})\n\nVue.config.errorHandler = function(err, _vm, info) {\n  airbrake.notify({\n    error: err,\n    params: {info: info}\n  })\n}\n```\n\n### Vue 3\n\nYou can start reporting errors from your Vue 3 app by configuring an\n[`errorHandler`](https://v3.vuejs.org/api/application-config.html#errorhandler)\nthat uses a `Notifier` initialized with your `projectId` and `projectKey`.\n\n```js\nimport { createApp } from \"vue\"\nimport App from \"./App.vue\"\nimport { Notifier } from '@airbrake/browser'\n\nvar airbrake = new Notifier({\n  projectId: 1,\n  projectKey: 'FIXME'\n})\n\nlet app = createApp(App)\n\napp.config.errorHandler = function(err, _vm, info) {\n  airbrake.notify({\n    error: err,\n    params: {info: info}\n  })\n}\n\napp.mount(\"#app\")\n```\n"
  },
  {
    "path": "packages/browser/jest.config.js",
    "content": "module.exports = {\n  transform: {\n    '^.+\\\\.jsx?$': 'babel-jest',\n    '^.+\\\\.tsx?$': 'ts-jest',\n  },\n  testEnvironment: 'jsdom',\n  roots: ['tests'],\n  clearMocks: true,\n};\n"
  },
  {
    "path": "packages/browser/package.json",
    "content": "{\n  \"name\": \"@airbrake/browser\",\n  \"version\": \"2.1.9\",\n  \"description\": \"Official Airbrake notifier for browsers\",\n  \"author\": \"Airbrake\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/airbrake/airbrake-js.git\",\n    \"directory\": \"packages/browser\"\n  },\n  \"homepage\": \"https://github.com/airbrake/airbrake-js/tree/master/packages/browser\",\n  \"keywords\": [\n    \"exception\",\n    \"error\",\n    \"airbrake\",\n    \"notifier\"\n  ],\n  \"dependencies\": {\n    \"@types/promise-polyfill\": \"^6.0.3\",\n    \"@types/request\": \"2.48.8\",\n    \"cross-fetch\": \"^3.1.5\",\n    \"error-stack-parser\": \"^2.0.4\",\n    \"promise-polyfill\": \"^8.1.3\",\n    \"tdigest\": \"^0.1.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.9.0\",\n    \"@babel/preset-env\": \"^7.9.0\",\n    \"@rollup/plugin-commonjs\": \"^24.0.0\",\n    \"@rollup/plugin-node-resolve\": \"^15.0.1\",\n    \"@rollup/plugin-typescript\": \"^11.0.0\",\n    \"babel-jest\": \"^29.3.1\",\n    \"jest\": \"^27.3.1\",\n    \"prettier\": \"^2.0.2\",\n    \"rollup\": \"^2.6.1\",\n    \"rollup-plugin-terser\": \"^7.0.0\",\n    \"ts-jest\": \"^27.1.0\",\n    \"tslib\": \"^2.0.0\",\n    \"tslint\": \"^6.1.0\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"tslint-plugin-prettier\": \"^2.3.0\",\n    \"typescript\": \"^4.0.2\"\n  },\n  \"main\": \"dist/index.js\",\n  \"module\": \"esm/index.js\",\n  \"unpkg\": \"umd/airbrake.js\",\n  \"jsdelivr\": \"umd/airbrake.js\",\n  \"files\": [\n    \"dist/\",\n    \"esm/\",\n    \"umd/\",\n    \"README.md\",\n    \"LICENSE\"\n  ],\n  \"scripts\": {\n    \"build\": \"yarn build:cjs && yarn build:esm && yarn build:umd\",\n    \"build:watch\": \"concurrently 'yarn build:cjs:watch' 'yarn build:esm:watch'\",\n    \"build:cjs\": \"tsc -p tsconfig.cjs.json\",\n    \"build:cjs:watch\": \"tsc -p tsconfig.cjs.json -w --preserveWatchOutput\",\n    \"build:esm\": \"tsc -p tsconfig.esm.json\",\n    \"build:esm:watch\": \"tsc -p tsconfig.esm.json -w --preserveWatchOutput\",\n    \"build:umd\": \"rollup --config\",\n    \"clean\": \"rm -rf dist esm umd\",\n    \"lint\": \"tslint -p .\",\n    \"test\": \"jest\"\n  }\n}"
  },
  {
    "path": "packages/browser/rollup.config.js",
    "content": "import commonjs from '@rollup/plugin-commonjs';\nimport resolve from '@rollup/plugin-node-resolve';\nimport typescript from '@rollup/plugin-typescript';\nimport { terser } from 'rollup-plugin-terser';\n\nconst pkg = require('./package.json');\n\nconst webPlugins = [\n  resolve({ browser: true }),\n  commonjs(),\n  typescript({ tsconfig: './tsconfig.umd.json' }),\n];\n\nfunction umd(cfg) {\n  return Object.assign(\n    {\n      format: 'umd',\n      banner: `/* airbrake-js v${pkg.version} */`,\n      sourcemap: true,\n      name: 'Airbrake',\n    },\n    cfg,\n  );\n}\n\nexport default {\n  input: 'src/index.ts',\n  output: [\n    umd({ file: 'umd/airbrake.js' }),\n    umd({ file: 'umd/airbrake.min.js', plugins: [terser()] }),\n  ],\n  plugins: webPlugins,\n};\n"
  },
  {
    "path": "packages/browser/src/base_notifier.ts",
    "content": "import Promise from 'promise-polyfill';\n\nimport { IFuncWrapper } from './func_wrapper';\nimport { jsonifyNotice } from './jsonify_notice';\nimport { INotice } from './notice';\nimport { Scope } from './scope';\n\nimport { espProcessor } from './processor/esp';\nimport { Processor } from './processor/processor';\n\nimport { angularMessageFilter } from './filter/angular_message';\nimport { makeDebounceFilter } from './filter/debounce';\nimport { Filter } from './filter/filter';\nimport { ignoreNoiseFilter } from './filter/ignore_noise';\nimport { uncaughtMessageFilter } from './filter/uncaught_message';\n\nimport { makeRequester, Requester } from './http_req';\n\nimport { IOptions } from './options';\nimport { QueriesStats } from './queries';\nimport { QueueMetric, QueuesStats } from './queues';\nimport { RouteMetric, RoutesBreakdowns, RoutesStats } from './routes';\nimport { NOTIFIER_NAME, NOTIFIER_VERSION, NOTIFIER_URL } from './version';\nimport { PerformanceFilter } from './filter/performance_filter';\nimport { RemoteSettings } from './remote_settings';\n\nexport class BaseNotifier {\n  routes: Routes;\n  queues: Queues;\n  queries: QueriesStats;\n\n  _opt: IOptions;\n  _url: string;\n\n  _processor: Processor;\n  _requester: Requester;\n  _filters: Filter[] = [];\n  _performanceFilters: PerformanceFilter[] = [];\n  _scope = new Scope();\n\n  _onClose: (() => void)[] = [];\n\n  constructor(opt: IOptions) {\n    if (!opt.projectId || !opt.projectKey) {\n      throw new Error('airbrake: projectId and projectKey are required');\n    }\n\n    this._opt = opt;\n    this._opt.host = this._opt.host || 'https://api.airbrake.io';\n    this._opt.remoteConfigHost =\n      this._opt.remoteConfigHost || 'https://notifier-configs.airbrake.io';\n    this._opt.apmHost = this._opt.apmHost || 'https://api.airbrake.io';\n    this._opt.timeout = this._opt.timeout || 10000;\n    this._opt.keysBlocklist = this._opt.keysBlocklist || [/password/, /secret/];\n    this._url = `${this._opt.host}/api/v3/projects/${this._opt.projectId}/notices?key=${this._opt.projectKey}`;\n\n    this._opt.errorNotifications = this._opt.errorNotifications !== false;\n    this._opt.performanceStats = this._opt.performanceStats !== false;\n\n    this._opt.queryStats = this._opt.queryStats !== false;\n    this._opt.queueStats = this._opt.queueStats !== false;\n\n    this._opt.remoteConfig = this._opt.remoteConfig !== false;\n\n    this._processor = this._opt.processor || espProcessor;\n    this._requester = makeRequester(this._opt);\n\n    this.addFilter(ignoreNoiseFilter);\n    this.addFilter(makeDebounceFilter());\n    this.addFilter(uncaughtMessageFilter);\n    this.addFilter(angularMessageFilter);\n\n    this.addFilter((notice: INotice): INotice | null => {\n      notice.context.notifier = {\n        name: NOTIFIER_NAME,\n        version: NOTIFIER_VERSION,\n        url: NOTIFIER_URL,\n      };\n      if (this._opt.environment) {\n        notice.context.environment = this._opt.environment;\n      }\n      return notice;\n    });\n\n    this.routes = new Routes(this);\n    this.queues = new Queues(this);\n    this.queries = new QueriesStats(this._opt);\n\n    if (this._opt.remoteConfig) {\n      const pollerId = new RemoteSettings(this._opt).poll();\n      this._onClose.push(() => clearInterval(pollerId));\n    }\n  }\n\n  close(): void {\n    for (let fn of this._onClose) {\n      fn();\n    }\n  }\n\n  scope(): Scope {\n    return this._scope;\n  }\n\n  setActiveScope(scope: Scope) {\n    this._scope = scope;\n  }\n\n  addFilter(filter: Filter): void {\n    this._filters.push(filter);\n  }\n\n  addPerformanceFilter(performanceFilter: PerformanceFilter) {\n    this._performanceFilters.push(performanceFilter);\n  }\n\n  notify(err: any): Promise<INotice> {\n    if (typeof err !== 'object' || err === null || !('error' in err)) {\n      err = { error: err };\n    }\n    this.handleFalseyError(err);\n\n    let notice = this.newNotice(err);\n\n    if (!this._opt.errorNotifications) {\n      notice.error = new Error(\n        `airbrake: not sending this error, errorNotifications is disabled err=${JSON.stringify(\n          err.error\n        )}`\n      );\n      return Promise.resolve(notice);\n    }\n\n    let error = this._processor(err.error);\n    notice.errors.push(error);\n\n    for (let filter of this._filters) {\n      let r = filter(notice);\n      if (r === null) {\n        notice.error = new Error('airbrake: error is filtered');\n        return Promise.resolve(notice);\n      }\n      notice = r;\n    }\n\n    if (!notice.context) {\n      notice.context = {};\n    }\n    notice.context.language = 'JavaScript';\n    return this._sendNotice(notice);\n  }\n\n  private handleFalseyError(err: any) {\n    if (Number.isNaN(err.error)) {\n      err.error = new Error('NaN');\n    } else if (err.error === undefined) {\n      err.error = new Error('undefined');\n    } else if (err.error === '') {\n      err.error = new Error('<empty string>');\n    } else if (err.error === null) {\n      err.error = new Error('null');\n    }\n  }\n\n  private newNotice(err: any): INotice {\n    return {\n      errors: [],\n      context: {\n        severity: 'error',\n        ...this.scope().context(),\n        ...err.context,\n      },\n      params: err.params || {},\n      environment: err.environment || {},\n      session: err.session || {},\n    };\n  }\n\n  _sendNotice(notice: INotice): Promise<INotice> {\n    let body = jsonifyNotice(notice, {\n      keysBlocklist: this._opt.keysBlocklist,\n    });\n    if (this._opt.reporter) {\n      if (typeof this._opt.reporter === 'function') {\n        return this._opt.reporter(notice);\n      } else {\n        console.warn('airbrake: options.reporter must be a function');\n      }\n    }\n\n    let req = {\n      method: 'POST',\n      url: this._url,\n      body,\n    };\n    return this._requester(req)\n      .then((resp) => {\n        notice.id = resp.json.id;\n        notice.url = resp.json.url;\n        return notice;\n      })\n      .catch((err) => {\n        notice.error = err;\n        return notice;\n      });\n  }\n\n  wrap(fn, props: string[] = []): IFuncWrapper {\n    if (fn._airbrake) {\n      return fn;\n    }\n\n    // tslint:disable-next-line:no-this-assignment\n    let client = this;\n    let airbrakeWrapper = function () {\n      let fnArgs = Array.prototype.slice.call(arguments);\n      let wrappedArgs = client._wrapArguments(fnArgs);\n      try {\n        return fn.apply(this, wrappedArgs);\n      } catch (err) {\n        client.notify({ error: err, params: { arguments: fnArgs } });\n        client._ignoreNextWindowError();\n        throw err;\n      }\n    } as IFuncWrapper;\n\n    for (let prop in fn) {\n      if (fn.hasOwnProperty(prop)) {\n        airbrakeWrapper[prop] = fn[prop];\n      }\n    }\n    for (let prop of props) {\n      if (fn.hasOwnProperty(prop)) {\n        airbrakeWrapper[prop] = fn[prop];\n      }\n    }\n\n    airbrakeWrapper._airbrake = true;\n    airbrakeWrapper.inner = fn;\n\n    return airbrakeWrapper;\n  }\n\n  _wrapArguments(args: any[]): any[] {\n    for (let i = 0; i < args.length; i++) {\n      let arg = args[i];\n      if (typeof arg === 'function') {\n        args[i] = this.wrap(arg);\n      }\n    }\n    return args;\n  }\n\n  _ignoreNextWindowError() {}\n\n  call(fn, ..._args: any[]): any {\n    let wrapper = this.wrap(fn);\n    return wrapper.apply(this, Array.prototype.slice.call(arguments, 1));\n  }\n}\n\nclass Routes {\n  _notifier: BaseNotifier;\n  _routes: RoutesStats;\n  _breakdowns: RoutesBreakdowns;\n  _opt: IOptions;\n\n  constructor(notifier: BaseNotifier) {\n    this._notifier = notifier;\n    this._routes = new RoutesStats(notifier._opt);\n    this._breakdowns = new RoutesBreakdowns(notifier._opt);\n    this._opt = notifier._opt;\n  }\n\n  start(\n    method = '',\n    route = '',\n    statusCode = 0,\n    contentType = ''\n  ): RouteMetric {\n    const metric = new RouteMetric(method, route, statusCode, contentType);\n\n    if (!this._opt.performanceStats) {\n      return metric;\n    }\n\n    const scope = this._notifier.scope().clone();\n    scope.setContext({ httpMethod: method, route });\n    scope.setRouteMetric(metric);\n    this._notifier.setActiveScope(scope);\n\n    return metric;\n  }\n\n  notify(req: RouteMetric): void {\n    if (!this._opt.performanceStats) {\n      return;\n    }\n\n    req.end();\n\n    for (const performanceFilter of this._notifier._performanceFilters) {\n      if (performanceFilter(req) === null) {\n        return;\n      }\n    }\n    this._routes.notify(req);\n    this._breakdowns.notify(req);\n  }\n}\n\nclass Queues {\n  _notifier: BaseNotifier;\n  _queues: QueuesStats;\n  _opt: IOptions;\n\n  constructor(notifier: BaseNotifier) {\n    this._notifier = notifier;\n    this._queues = new QueuesStats(notifier._opt);\n    this._opt = notifier._opt;\n  }\n\n  start(queue: string): QueueMetric {\n    const metric = new QueueMetric(queue);\n\n    if (!this._opt.performanceStats) {\n      return metric;\n    }\n\n    const scope = this._notifier.scope().clone();\n    scope.setContext({ queue });\n    scope.setQueueMetric(metric);\n    this._notifier.setActiveScope(scope);\n\n    return metric;\n  }\n\n  notify(q: QueueMetric): void {\n    if (!this._opt.performanceStats) {\n      return;\n    }\n\n    q.end();\n    this._queues.notify(q);\n  }\n}\n"
  },
  {
    "path": "packages/browser/src/filter/angular_message.ts",
    "content": "import { INotice } from '../notice';\n\nlet re = new RegExp(\n  [\n    '^',\n    '\\\\[(\\\\$.+)\\\\]', // type\n    '\\\\s',\n    '([\\\\s\\\\S]+)', // message\n    '$',\n  ].join('')\n);\n\nexport function angularMessageFilter(notice: INotice): INotice {\n  let err = notice.errors[0];\n  if (err.type !== '' && err.type !== 'Error') {\n    return notice;\n  }\n\n  let m = err.message.match(re);\n  if (m !== null) {\n    err.type = m[1];\n    err.message = m[2];\n  }\n\n  return notice;\n}\n"
  },
  {
    "path": "packages/browser/src/filter/debounce.ts",
    "content": "import { INotice } from '../notice';\nimport { Filter } from './filter';\n\nexport function makeDebounceFilter(): Filter {\n  let lastNoticeJSON: string;\n  let timeout;\n\n  return (notice: INotice): INotice | null => {\n    let s = JSON.stringify(notice.errors);\n    if (s === lastNoticeJSON) {\n      return null;\n    }\n\n    if (timeout) {\n      clearTimeout(timeout);\n    }\n\n    lastNoticeJSON = s;\n    timeout = setTimeout(() => {\n      lastNoticeJSON = '';\n    }, 1000);\n\n    return notice;\n  };\n}\n"
  },
  {
    "path": "packages/browser/src/filter/filter.ts",
    "content": "import { INotice } from '../notice';\n\nexport type Filter = (notice: INotice) => INotice | null;\n"
  },
  {
    "path": "packages/browser/src/filter/ignore_noise.ts",
    "content": "import { INotice } from '../notice';\n\nconst IGNORED_MESSAGES = [\n  'Script error',\n  'Script error.',\n  'InvalidAccessError',\n];\n\nexport function ignoreNoiseFilter(notice: INotice): INotice | null {\n  let err = notice.errors[0];\n  if (err.type === '' && IGNORED_MESSAGES.indexOf(err.message) !== -1) {\n    return null;\n  }\n\n  if (err.backtrace && err.backtrace.length > 0) {\n    let frame = err.backtrace[0];\n    if (frame.file === '<anonymous>') {\n      return null;\n    }\n  }\n\n  return notice;\n}\n"
  },
  {
    "path": "packages/browser/src/filter/performance_filter.ts",
    "content": "import { RouteMetric } from '../routes';\n\nexport type PerformanceFilter = (metric: RouteMetric) => RouteMetric | null;\n"
  },
  {
    "path": "packages/browser/src/filter/uncaught_message.ts",
    "content": "import { INotice } from '../notice';\n\nlet re = new RegExp(\n  [\n    '^',\n    'Uncaught\\\\s',\n    '(.+?)', // type\n    ':\\\\s',\n    '(.+)', // message\n    '$',\n  ].join('')\n);\nexport function uncaughtMessageFilter(notice: INotice): INotice {\n  let err = notice.errors[0];\n  if (err.type !== '' && err.type !== 'Error') {\n    return notice;\n  }\n\n  let m = err.message.match(re);\n  if (m !== null) {\n    err.type = m[1];\n    err.message = m[2];\n  }\n\n  return notice;\n}\n"
  },
  {
    "path": "packages/browser/src/filter/window.ts",
    "content": "import { INotice } from '../notice';\n\nexport function windowFilter(notice: INotice): INotice {\n  if (window.navigator && window.navigator.userAgent) {\n    notice.context.userAgent = window.navigator.userAgent;\n  }\n  if (window.location) {\n    notice.context.url = String(window.location);\n    // Set root directory to group errors on different subdomains together.\n    notice.context.rootDirectory =\n      window.location.protocol + '//' + window.location.host;\n  }\n  return notice;\n}\n"
  },
  {
    "path": "packages/browser/src/func_wrapper.ts",
    "content": "export interface IFuncWrapper {\n  (): any;\n  inner: () => any;\n  _airbrake?: boolean;\n}\n"
  },
  {
    "path": "packages/browser/src/http_req/api.ts",
    "content": "export interface IHttpRequest {\n  method: string;\n  url: string;\n  body?: string;\n  timeout?: number;\n  headers?: any;\n}\n\nexport interface IHttpResponse {\n  json: any;\n}\n\nexport type Requester = (req: IHttpRequest) => Promise<IHttpResponse>;\n\nexport let errors = {\n  unauthorized: new Error(\n    'airbrake: unauthorized: project id or key are wrong'\n  ),\n  ipRateLimited: new Error('airbrake: IP is rate limited'),\n};\n"
  },
  {
    "path": "packages/browser/src/http_req/fetch.ts",
    "content": "import fetch from 'cross-fetch';\nimport Promise from 'promise-polyfill';\nimport { errors, IHttpRequest, IHttpResponse } from './api';\n\nlet rateLimitReset = 0;\n\nexport function request(req: IHttpRequest): Promise<IHttpResponse> {\n  let utime = Date.now() / 1000;\n  if (utime < rateLimitReset) {\n    return Promise.reject(errors.ipRateLimited);\n  }\n\n  let opt = {\n    method: req.method,\n    body: req.body,\n    headers: req.headers,\n  };\n  return fetch(req.url, opt).then((resp: Response) => {\n    if (resp.status === 401) {\n      throw errors.unauthorized;\n    }\n\n    if (resp.status === 429) {\n      let s = resp.headers.get('X-RateLimit-Delay');\n      if (!s) {\n        throw errors.ipRateLimited;\n      }\n\n      let n = parseInt(s, 10);\n      if (n > 0) {\n        rateLimitReset = Date.now() / 1000 + n;\n      }\n\n      throw errors.ipRateLimited;\n    }\n\n    if (resp.status === 204) {\n      return { json: null };\n    }\n    if (resp.status === 404) {\n      throw new Error('404 Not Found');\n    }\n\n    if (resp.status >= 200 && resp.status < 300) {\n      return resp.json().then((json) => {\n        return { json };\n      });\n    }\n\n    if (resp.status >= 400 && resp.status < 500) {\n      return resp.json().then((json) => {\n        let err = new Error(json.message);\n        throw err;\n      });\n    }\n\n    return resp.text().then((body) => {\n      let err = new Error(\n        `airbrake: fetch: unexpected response: code=${resp.status} body='${body}'`\n      );\n      throw err;\n    });\n  });\n}\n"
  },
  {
    "path": "packages/browser/src/http_req/index.ts",
    "content": "import { IOptions } from '../options';\nimport { Requester } from './api';\nimport { request as fetchRequest } from './fetch';\nimport { makeRequester as makeNodeRequester } from './node';\n\nexport { Requester };\n\nexport function makeRequester(opts: IOptions): Requester {\n  if (opts.request) {\n    return makeNodeRequester(opts.request);\n  }\n  return fetchRequest;\n}\n"
  },
  {
    "path": "packages/browser/src/http_req/node.ts",
    "content": "import * as request_lib from 'request';\nimport Promise from 'promise-polyfill';\n\nimport { errors, IHttpRequest, IHttpResponse, Requester } from './api';\n\ntype requestAPI = request_lib.RequestAPI<\n  request_lib.Request,\n  request_lib.CoreOptions,\n  request_lib.RequiredUriUrl\n>;\n\nexport function makeRequester(api: requestAPI): Requester {\n  return (req: IHttpRequest): Promise<IHttpResponse> => {\n    return request(req, api);\n  };\n}\n\nlet rateLimitReset = 0;\n\nfunction request(req: IHttpRequest, api: requestAPI): Promise<IHttpResponse> {\n  let utime = Date.now() / 1000;\n  if (utime < rateLimitReset) {\n    return Promise.reject(errors.ipRateLimited);\n  }\n\n  return new Promise((resolve, reject) => {\n    api(\n      {\n        url: req.url,\n        method: req.method,\n        body: req.body,\n        headers: {\n          'content-type': 'application/json',\n        },\n        timeout: req.timeout,\n      },\n      (error: any, resp: request_lib.RequestResponse, body: any): void => {\n        if (error) {\n          reject(error);\n          return;\n        }\n\n        if (!resp.statusCode) {\n          error = new Error(\n            `airbrake: request: response statusCode is ${resp.statusCode}`\n          );\n          reject(error);\n          return;\n        }\n\n        if (resp.statusCode === 401) {\n          reject(errors.unauthorized);\n          return;\n        }\n\n        if (resp.statusCode === 429) {\n          reject(errors.ipRateLimited);\n\n          let h = resp.headers['x-ratelimit-delay'];\n          if (!h) {\n            return;\n          }\n\n          let s: string;\n          if (typeof h === 'string') {\n            s = h;\n          } else if (h instanceof Array) {\n            s = h[0];\n          } else {\n            return;\n          }\n\n          let n = parseInt(s, 10);\n          if (n > 0) {\n            rateLimitReset = Date.now() / 1000 + n;\n          }\n\n          return;\n        }\n\n        if (resp.statusCode === 204) {\n          resolve({ json: null });\n          return;\n        }\n\n        if (resp.statusCode >= 200 && resp.statusCode < 300) {\n          let json;\n          try {\n            json = JSON.parse(body);\n          } catch (err) {\n            reject(err);\n            return;\n          }\n          resolve(json);\n          return;\n        }\n\n        if (resp.statusCode >= 400 && resp.statusCode < 500) {\n          let json;\n          try {\n            json = JSON.parse(body);\n          } catch (err) {\n            reject(err);\n            return;\n          }\n          error = new Error(json.message);\n          reject(error);\n          return;\n        }\n\n        body = body.trim();\n        error = new Error(\n          `airbrake: node: unexpected response: code=${resp.statusCode} body='${body}'`\n        );\n        reject(error);\n      }\n    );\n  });\n}\n"
  },
  {
    "path": "packages/browser/src/index.ts",
    "content": "export { Notifier } from './notifier';\nexport { BaseNotifier } from './base_notifier';\nexport { INotice } from './notice';\nexport { IOptions } from './options';\nexport { QueryInfo } from './queries';\nexport { Scope } from './scope';\n"
  },
  {
    "path": "packages/browser/src/instrumentation/console.ts",
    "content": "import { IFuncWrapper } from '../func_wrapper';\nimport { Notifier } from '../notifier';\n\nconst CONSOLE_METHODS = ['debug', 'log', 'info', 'warn', 'error'];\n\nexport function instrumentConsole(notifier: Notifier): void {\n  // tslint:disable-next-line:no-this-assignment\n  for (let m of CONSOLE_METHODS) {\n    if (!(m in console)) {\n      continue;\n    }\n\n    const oldFn = console[m];\n    let newFn = ((...args) => {\n      oldFn.apply(console, args);\n      notifier.scope().pushHistory({\n        type: 'log',\n        severity: m,\n        arguments: args,\n      });\n    }) as IFuncWrapper;\n    newFn.inner = oldFn;\n    console[m] = newFn;\n  }\n}\n"
  },
  {
    "path": "packages/browser/src/instrumentation/dom.ts",
    "content": "import { Notifier } from '../notifier';\n\nconst elemAttrs = ['type', 'name', 'src'];\n\nexport function instrumentDOM(notifier: Notifier) {\n  const handler = makeEventHandler(notifier);\n\n  if (window.addEventListener) {\n    window.addEventListener('load', handler);\n    window.addEventListener(\n      'error',\n      (event: Event): void => {\n        if (getProp(event, 'error')) {\n          return;\n        }\n        handler(event);\n      },\n      true\n    );\n  }\n\n  if (typeof document === 'object' && document.addEventListener) {\n    document.addEventListener('DOMContentLoaded', handler);\n    document.addEventListener('click', handler);\n    document.addEventListener('keypress', handler);\n  }\n}\n\nfunction makeEventHandler(notifier: Notifier): EventListener {\n  return (event: Event): void => {\n    let target = getProp(event, 'target') as HTMLElement | null;\n    if (!target) {\n      return;\n    }\n\n    let state: any = { type: event.type };\n\n    try {\n      state.target = elemPath(target);\n    } catch (err) {\n      state.target = `<${String(err)}>`;\n    }\n\n    notifier.scope().pushHistory(state);\n  };\n}\n\nfunction elemName(elem: HTMLElement): string {\n  if (!elem) {\n    return '';\n  }\n\n  let s: string[] = [];\n\n  if (elem.tagName) {\n    s.push(elem.tagName.toLowerCase());\n  }\n\n  if (elem.id) {\n    s.push('#');\n    s.push(elem.id);\n  }\n\n  if (elem.classList && Array.from) {\n    s.push('.');\n    s.push(Array.from(elem.classList).join('.'));\n  } else if (elem.className) {\n    let str = classNameString(elem.className);\n    if (str !== '') {\n      s.push('.');\n      s.push(str);\n    }\n  }\n\n  if (elem.getAttribute) {\n    for (let attr of elemAttrs) {\n      let value = elem.getAttribute(attr);\n      if (value) {\n        s.push(`[${attr}=\"${value}\"]`);\n      }\n    }\n  }\n\n  return s.join('');\n}\n\nfunction classNameString(name: any): string {\n  if (name.split) {\n    return name.split(' ').join('.');\n  }\n  if (name.baseVal && name.baseVal.split) {\n    // SVGAnimatedString\n    return name.baseVal.split(' ').join('.');\n  }\n  console.error('unsupported HTMLElement.className type', typeof name);\n  return '';\n}\n\nfunction elemPath(elem: HTMLElement): string {\n  const maxLen = 10;\n\n  let path: string[] = [];\n\n  let parent = elem;\n  while (parent) {\n    let name = elemName(parent);\n    if (name !== '') {\n      path.push(name);\n      if (path.length > maxLen) {\n        break;\n      }\n    }\n    parent = parent.parentNode as HTMLElement;\n  }\n\n  if (path.length === 0) {\n    return String(elem);\n  }\n\n  return path.reverse().join(' > ');\n}\n\nfunction getProp(obj: any, prop: string): any {\n  try {\n    return obj[prop];\n  } catch (_) {\n    // Permission denied to access property\n    return null;\n  }\n}\n"
  },
  {
    "path": "packages/browser/src/instrumentation/fetch.ts",
    "content": "import { Notifier } from '../notifier';\n\nexport function instrumentFetch(notifier: Notifier): void {\n  // tslint:disable-next-line:no-this-assignment\n  let oldFetch = window.fetch;\n  window.fetch = function (\n    req: RequestInfo,\n    options?: RequestInit\n  ): Promise<Response> {\n    let state: any = {\n      type: 'xhr',\n      date: new Date(),\n    };\n\n    state.method = options && options.method ? options.method : 'GET';\n    if (typeof req === 'string') {\n      state.url = req;\n    } else {\n      state.method = req.method;\n      state.url = req.url;\n    }\n\n    // Some platforms (e.g. react-native) implement fetch via XHR.\n    notifier._ignoreNextXHR++;\n    setTimeout(() => notifier._ignoreNextXHR--);\n\n    return oldFetch\n      .apply(this, arguments)\n      .then((resp: Response) => {\n        state.statusCode = resp.status;\n        state.duration = new Date().getTime() - state.date.getTime();\n        notifier.scope().pushHistory(state);\n        return resp;\n      })\n      .catch((err) => {\n        state.error = err;\n        state.duration = new Date().getTime() - state.date.getTime();\n        notifier.scope().pushHistory(state);\n        throw err;\n      });\n  };\n}\n"
  },
  {
    "path": "packages/browser/src/instrumentation/location.ts",
    "content": "import { Notifier } from '../notifier';\n\nlet lastLocation = '';\n\n// In some environments (i.e. Cypress) document.location may sometimes be null\nfunction getCurrentLocation(): string | null {\n  return document.location && document.location.pathname;\n}\n\nexport function instrumentLocation(notifier: Notifier): void {\n  lastLocation = getCurrentLocation();\n\n  const oldFn = window.onpopstate;\n  window.onpopstate = function abOnpopstate(_event: PopStateEvent): any {\n    const url = getCurrentLocation();\n    if (url) {\n      recordLocation(notifier, url);\n    }\n    if (oldFn) {\n      return oldFn.apply(this, arguments);\n    }\n  };\n\n  const oldPushState = history.pushState;\n  history.pushState = function abPushState(\n    _state: any,\n    _title: string,\n    url?: string | null\n  ): void {\n    if (url) {\n      recordLocation(notifier, url.toString());\n    }\n    oldPushState.apply(this, arguments);\n  };\n}\n\nfunction recordLocation(notifier: Notifier, url: string): void {\n  let index = url.indexOf('://');\n  if (index >= 0) {\n    url = url.slice(index + 3);\n    index = url.indexOf('/');\n    url = index >= 0 ? url.slice(index) : '/';\n  } else if (url.charAt(0) !== '/') {\n    url = '/' + url;\n  }\n\n  notifier.scope().pushHistory({\n    type: 'location',\n    from: lastLocation,\n    to: url,\n  });\n  lastLocation = url;\n}\n"
  },
  {
    "path": "packages/browser/src/instrumentation/unhandledrejection.ts",
    "content": "import { Notifier } from '../notifier';\n\nexport function instrumentUnhandledrejection(notifier: Notifier): void {\n  const handler = onUnhandledrejection.bind(notifier);\n\n  window.addEventListener('unhandledrejection', handler);\n  notifier._onClose.push(() => {\n    window.removeEventListener('unhandledrejection', handler);\n  });\n}\n\nfunction onUnhandledrejection(e: any): void {\n  // Handle native or bluebird Promise rejections\n  // https://developer.mozilla.org/en-US/docs/Web/Events/unhandledrejection\n  // http://bluebirdjs.com/docs/api/error-management-configuration.html\n  let reason = e.reason || (e.detail && e.detail.reason);\n  if (!reason) return;\n\n  let msg = reason.message || String(reason);\n  if (msg.indexOf && msg.indexOf('airbrake: ') === 0) return;\n\n  if (typeof reason !== 'object' || reason.error === undefined) {\n    this.notify({\n      error: reason,\n      context: {\n        unhandledRejection: true,\n      },\n    });\n    return;\n  }\n\n  this.notify({ ...reason, context: { unhandledRejection: true } });\n}\n"
  },
  {
    "path": "packages/browser/src/instrumentation/xhr.ts",
    "content": "import { Notifier } from '../notifier';\n\ninterface IXMLHttpRequestWithState extends XMLHttpRequest {\n  __state: any;\n}\n\nexport function instrumentXHR(notifier: Notifier): void {\n  function recordReq(req: IXMLHttpRequestWithState): void {\n    const state = req.__state;\n    state.statusCode = req.status;\n    state.duration = new Date().getTime() - state.date.getTime();\n    notifier.scope().pushHistory(state);\n  }\n\n  const oldOpen = XMLHttpRequest.prototype.open;\n  XMLHttpRequest.prototype.open = function abOpen(\n    method: string,\n    url: string,\n    _async?: boolean,\n    _user?: string,\n    _password?: string\n  ): void {\n    if (notifier._ignoreNextXHR === 0) {\n      this.__state = {\n        type: 'xhr',\n        method,\n        url,\n      };\n    }\n    oldOpen.apply(this, arguments);\n  };\n\n  const oldSend = XMLHttpRequest.prototype.send;\n  XMLHttpRequest.prototype.send = function abSend(_data?: any): void {\n    let oldFn = this.onreadystatechange;\n    this.onreadystatechange = function (_ev: Event): any {\n      if (this.readyState === 4 && this.__state) {\n        recordReq(this);\n      }\n      if (oldFn) {\n        return oldFn.apply(this, arguments);\n      }\n    };\n\n    if (this.__state) {\n      (this as IXMLHttpRequestWithState).__state.date = new Date();\n    }\n    return oldSend.apply(this, arguments);\n  };\n}\n"
  },
  {
    "path": "packages/browser/src/jsonify_notice.ts",
    "content": "import { INotice } from './notice';\n\nconst FILTERED = '[Filtered]';\nconst MAX_OBJ_LENGTH = 128;\n\n// jsonifyNotice serializes notice to JSON and truncates params,\n// environment and session keys.\nexport function jsonifyNotice(\n  notice: INotice,\n  { maxLength = 64000, keysBlocklist = [], keysAllowlist = [] } = {}\n): string {\n  if (notice.errors) {\n    for (let i = 0; i < notice.errors.length; i++) {\n      let t = new Truncator({ keysBlocklist, keysAllowlist });\n      notice.errors[i] = t.truncate(notice.errors[i]);\n    }\n  }\n\n  let s = '';\n  let keys = ['params', 'environment', 'session'];\n  for (let level = 0; level < 8; level++) {\n    let opts = { level, keysBlocklist, keysAllowlist };\n    for (let key of keys) {\n      let obj = notice[key];\n      if (obj) {\n        notice[key] = truncate(obj, opts);\n      }\n    }\n\n    s = JSON.stringify(notice);\n    if (s.length < maxLength) {\n      return s;\n    }\n  }\n\n  let params = {\n    json: s.slice(0, Math.floor(maxLength / 2)) + '...',\n  };\n  keys.push('errors');\n  for (let key of keys) {\n    let obj = notice[key];\n    if (!obj) {\n      continue;\n    }\n\n    s = JSON.stringify(obj);\n    params[key] = s.length;\n  }\n\n  let err = new Error(\n    `airbrake: notice exceeds max length and can't be truncated`\n  );\n  (err as any).params = params;\n  throw err;\n}\n\nfunction scale(num: number, level: number): number {\n  return num >> level || 1;\n}\n\ninterface ITruncatorOptions {\n  level?: number;\n  keysBlocklist?: any[];\n  keysAllowlist?: any[];\n}\n\nclass Truncator {\n  private maxStringLength = 1024;\n  private maxObjectLength = MAX_OBJ_LENGTH;\n  private maxArrayLength = MAX_OBJ_LENGTH;\n  private maxDepth = 8;\n\n  private keys: string[] = [];\n  private keysBlocklist: any[] = [];\n  private keysAllowlist: any[] = [];\n  private seen: any[] = [];\n\n  constructor(opts: ITruncatorOptions) {\n    let level = opts.level || 0;\n    this.keysBlocklist = opts.keysBlocklist || [];\n    this.keysAllowlist = opts.keysAllowlist || [];\n\n    this.maxStringLength = scale(this.maxStringLength, level);\n    this.maxObjectLength = scale(this.maxObjectLength, level);\n    this.maxArrayLength = scale(this.maxArrayLength, level);\n    this.maxDepth = scale(this.maxDepth, level);\n  }\n\n  public truncate(value: any, key = '', depth = 0): any {\n    if (value === null || value === undefined) {\n      return value;\n    }\n\n    switch (typeof value) {\n      case 'boolean':\n      case 'number':\n      case 'function':\n        return value;\n      case 'string':\n        return this.truncateString(value);\n      case 'object':\n        break;\n      default:\n        return this.truncateString(String(value));\n    }\n\n    if (value instanceof String) {\n      return this.truncateString(value.toString());\n    }\n\n    if (\n      value instanceof Boolean ||\n      value instanceof Number ||\n      value instanceof Date ||\n      value instanceof RegExp\n    ) {\n      return value;\n    }\n\n    if (value instanceof Error) {\n      return this.truncateString(value.toString());\n    }\n\n    if (this.seen.indexOf(value) >= 0) {\n      return `[Circular ${this.getPath(value)}]`;\n    }\n\n    let type = objectType(value);\n\n    depth++;\n    if (depth > this.maxDepth) {\n      return `[Truncated ${type}]`;\n    }\n\n    this.keys.push(key);\n    this.seen.push(value);\n\n    switch (type) {\n      case 'Array':\n        return this.truncateArray(value, depth);\n      case 'Object':\n        return this.truncateObject(value, depth);\n      default:\n        let saved = this.maxDepth;\n        this.maxDepth = 0;\n\n        let obj = this.truncateObject(value, depth);\n        obj.__type = type;\n\n        this.maxDepth = saved;\n\n        return obj;\n    }\n  }\n\n  private getPath(value): string {\n    let index = this.seen.indexOf(value);\n    let path = [this.keys[index]];\n    for (let i = index; i >= 0; i--) {\n      let sub = this.seen[i];\n      if (sub && getAttr(sub, path[0]) === value) {\n        value = sub;\n        path.unshift(this.keys[i]);\n      }\n    }\n    return '~' + path.join('.');\n  }\n\n  private truncateString(s: string): string {\n    if (s.length > this.maxStringLength) {\n      return s.slice(0, this.maxStringLength) + '...';\n    }\n    return s;\n  }\n\n  private truncateArray(arr: any[], depth = 0): any[] {\n    let length = 0;\n    let dst: any = [];\n    for (let i = 0; i < arr.length; i++) {\n      let el = arr[i];\n      dst.push(this.truncate(el, i.toString(), depth));\n\n      length++;\n      if (length >= this.maxArrayLength) {\n        break;\n      }\n    }\n    return dst;\n  }\n\n  private truncateObject(obj: any, depth = 0): any {\n    let length = 0;\n    let dst = {};\n    for (let key in obj) {\n      if (!Object.prototype.hasOwnProperty.call(obj, key)) {\n        continue;\n      }\n\n      if (this.filterKey(key, dst)) continue;\n\n      let value = getAttr(obj, key);\n\n      if (value === undefined || typeof value === 'function') {\n        continue;\n      }\n      dst[key] = this.truncate(value, key, depth);\n\n      length++;\n      if (length >= this.maxObjectLength) {\n        break;\n      }\n    }\n    return dst;\n  }\n\n  private filterKey(key: string, obj: any): boolean {\n    if (\n      (this.keysAllowlist.length > 0 && !isInList(key, this.keysAllowlist)) ||\n      (this.keysAllowlist.length === 0 && isInList(key, this.keysBlocklist))\n    ) {\n      obj[key] = FILTERED;\n      return true;\n    }\n\n    return false;\n  }\n}\n\nexport function truncate(value: any, opts: ITruncatorOptions = {}): any {\n  let t = new Truncator(opts);\n  return t.truncate(value);\n}\n\nfunction getAttr(obj: any, attr: string): any {\n  // Ignore browser specific exception trying to read an attribute (#79).\n  try {\n    return obj[attr];\n  } catch (_) {\n    return;\n  }\n}\n\nfunction objectType(obj: any): string {\n  let s = Object.prototype.toString.apply(obj);\n  return s.slice('[object '.length, -1);\n}\n\nfunction isInList(key: string, list: any[]): boolean {\n  for (let v of list) {\n    if (v === key) {\n      return true;\n    }\n    if (v instanceof RegExp) {\n      if (key.match(v)) {\n        return true;\n      }\n    }\n  }\n  return false;\n}\n"
  },
  {
    "path": "packages/browser/src/metrics.ts",
    "content": "export interface IMetric {\n  isRecording(): boolean;\n  startSpan(name: string, startTime?: Date): void;\n  endSpan(name: string, endTime?: Date): void;\n  _incGroup(name: string, ms: number): void;\n}\n\nexport class Span {\n  _metric: IMetric;\n\n  name: string;\n  startTime: Date;\n  endTime: Date;\n\n  _dur = 0;\n  _level = 0;\n\n  constructor(metric: IMetric, name: string, startTime?: Date) {\n    this._metric = metric;\n\n    this.name = name;\n    this.startTime = startTime || new Date();\n  }\n\n  end(endTime?: Date) {\n    this.endTime = endTime ? endTime : new Date();\n\n    this._dur += this.endTime.getTime() - this.startTime.getTime();\n    this._metric._incGroup(this.name, this._dur);\n    this._metric = null;\n  }\n\n  _pause() {\n    if (this._paused()) {\n      return;\n    }\n    let now = new Date();\n    this._dur += now.getTime() - this.startTime.getTime();\n    this.startTime = null;\n  }\n\n  _resume() {\n    if (!this._paused()) {\n      return;\n    }\n    this.startTime = new Date();\n  }\n\n  _paused() {\n    return this.startTime == null;\n  }\n}\n\nexport class BaseMetric implements IMetric {\n  startTime: Date;\n  endTime: Date;\n\n  _spans = {};\n  _groups = {};\n\n  constructor() {\n    this.startTime = new Date();\n  }\n\n  end(endTime?: Date): void {\n    if (!this.endTime) {\n      this.endTime = endTime || new Date();\n    }\n  }\n\n  isRecording(): boolean {\n    return true;\n  }\n\n  startSpan(name: string, startTime?: Date): void {\n    let span = this._spans[name];\n    if (span) {\n      span._level++;\n    } else {\n      span = new Span(this, name, startTime);\n      this._spans[name] = span;\n    }\n  }\n\n  endSpan(name: string, endTime?: Date): void {\n    let span = this._spans[name];\n    if (!span) {\n      console.error('airbrake: span=%s does not exist', name);\n      return;\n    }\n\n    if (span._level > 0) {\n      span._level--;\n    } else {\n      span.end(endTime);\n      delete this._spans[span.name];\n    }\n  }\n\n  _incGroup(name: string, ms: number): void {\n    this._groups[name] = (this._groups[name] || 0) + ms;\n  }\n\n  _duration(): number {\n    if (!this.endTime) {\n      this.endTime = new Date();\n    }\n    return this.endTime.getTime() - this.startTime.getTime();\n  }\n}\n\nexport class NoopMetric implements IMetric {\n  isRecording(): boolean {\n    return false;\n  }\n  startSpan(_name: string, _startTime?: Date): void {}\n  endSpan(_name: string, _startTime?: Date): void {}\n  _incGroup(_name: string, _ms: number): void {}\n}\n"
  },
  {
    "path": "packages/browser/src/notice.ts",
    "content": "export interface INoticeFrame {\n  function: string;\n  file: string;\n  line: number;\n  column: number;\n}\n\nexport interface INoticeError {\n  type: string;\n  message: string;\n  backtrace: INoticeFrame[];\n}\n\nexport interface INotice {\n  id?: string;\n  url?: string;\n  error?: Error;\n\n  errors?: INoticeError[];\n  context?: any;\n  params?: any;\n  session?: any;\n  environment?: any;\n}\n"
  },
  {
    "path": "packages/browser/src/notifier.ts",
    "content": "import Promise from 'promise-polyfill';\nimport { BaseNotifier } from './base_notifier';\nimport { windowFilter } from './filter/window';\nimport { instrumentConsole } from './instrumentation/console';\nimport { instrumentDOM } from './instrumentation/dom';\nimport { instrumentFetch } from './instrumentation/fetch';\nimport { instrumentLocation } from './instrumentation/location';\nimport { instrumentXHR } from './instrumentation/xhr';\nimport { instrumentUnhandledrejection } from './instrumentation/unhandledrejection';\nimport { INotice } from './notice';\nimport { IInstrumentationOptions, IOptions } from './options';\n\ninterface ITodo {\n  err: any;\n  resolve: (notice: INotice) => void;\n  reject: (err: Error) => void;\n}\n\nexport class Notifier extends BaseNotifier {\n  protected offline = false;\n  protected todo: ITodo[] = [];\n\n  _ignoreWindowError = 0;\n  _ignoreNextXHR = 0;\n\n  constructor(opt: IOptions) {\n    super(opt);\n\n    if (typeof window === 'undefined') {\n      return;\n    }\n\n    this.addFilter(windowFilter);\n\n    if (window.addEventListener) {\n      this.onOnline = this.onOnline.bind(this);\n      window.addEventListener('online', this.onOnline);\n      this.onOffline = this.onOffline.bind(this);\n      window.addEventListener('offline', this.onOffline);\n\n      this._onClose.push(() => {\n        window.removeEventListener('online', this.onOnline);\n        window.removeEventListener('offline', this.onOffline);\n      });\n    }\n\n    this._instrument(opt.instrumentation);\n  }\n\n  _instrument(opt: IInstrumentationOptions = {}) {\n    if (opt.console === undefined) {\n      opt.console = !isDevEnv(this._opt.environment);\n    }\n\n    if (enabled(opt.onerror)) {\n      // tslint:disable-next-line:no-this-assignment\n      let self = this;\n      let oldHandler = window.onerror;\n      window.onerror = function abOnerror() {\n        if (oldHandler) {\n          oldHandler.apply(this, arguments);\n        }\n        self.onerror.apply(self, arguments);\n      };\n    }\n\n    instrumentDOM(this);\n    if (enabled(opt.fetch) && typeof fetch === 'function') {\n      instrumentFetch(this);\n    }\n    if (enabled(opt.history) && typeof history === 'object') {\n      instrumentLocation(this);\n    }\n    if (enabled(opt.console) && typeof console === 'object') {\n      instrumentConsole(this);\n    }\n    if (enabled(opt.xhr) && typeof XMLHttpRequest !== 'undefined') {\n      instrumentXHR(this);\n    }\n    if (\n      enabled(opt.unhandledrejection) &&\n      typeof addEventListener === 'function'\n    ) {\n      instrumentUnhandledrejection(this);\n    }\n  }\n\n  public notify(err: any): Promise<INotice> {\n    if (this.offline) {\n      return new Promise((resolve, reject) => {\n        this.todo.push({\n          err,\n          resolve,\n          reject,\n        });\n        while (this.todo.length > 100) {\n          let j = this.todo.shift();\n          if (j === undefined) {\n            break;\n          }\n          j.resolve({\n            error: new Error('airbrake: offline queue is too large'),\n          });\n        }\n      });\n    }\n\n    return super.notify(err);\n  }\n\n  protected onOnline(): void {\n    this.offline = false;\n\n    for (let j of this.todo) {\n      this.notify(j.err).then((notice) => {\n        j.resolve(notice);\n      });\n    }\n    this.todo = [];\n  }\n\n  protected onOffline(): void {\n    this.offline = true;\n  }\n\n  onerror(\n    message: string,\n    filename?: string,\n    line?: number,\n    column?: number,\n    err?: Error\n  ): void {\n    if (this._ignoreWindowError > 0) {\n      return;\n    }\n\n    if (err) {\n      this.notify({\n        error: err,\n        context: {\n          windowError: true,\n        },\n      });\n      return;\n    }\n\n    // Ignore errors without file or line.\n    if (!filename || !line) {\n      return;\n    }\n\n    this.notify({\n      error: {\n        message,\n        fileName: filename,\n        lineNumber: line,\n        columnNumber: column,\n        noStack: true,\n      },\n      context: {\n        windowError: true,\n      },\n    });\n  }\n\n  _ignoreNextWindowError(): void {\n    this._ignoreWindowError++;\n    setTimeout(() => this._ignoreWindowError--);\n  }\n}\n\nfunction isDevEnv(env: any): boolean {\n  return env && env.startsWith && env.startsWith('dev');\n}\n\nfunction enabled(v: undefined | boolean): boolean {\n  return v === undefined || v === true;\n}\n"
  },
  {
    "path": "packages/browser/src/options.ts",
    "content": "import * as request from 'request';\n\nimport { INotice } from './notice';\nimport { Processor } from './processor/processor';\n\ntype Reporter = (notice: INotice) => Promise<INotice>;\n\nexport interface IInstrumentationOptions {\n  onerror?: boolean;\n  fetch?: boolean;\n  history?: boolean;\n  console?: boolean;\n  xhr?: boolean;\n  unhandledrejection?: boolean;\n}\n\nexport interface IOptions {\n  projectId: number;\n  projectKey: string;\n  environment?: string;\n  host?: string;\n  apmHost?: string;\n  remoteConfigHost?: string;\n  remoteConfig?: boolean;\n  timeout?: number;\n  keysBlocklist?: any[];\n  processor?: Processor;\n  reporter?: Reporter;\n  instrumentation?: IInstrumentationOptions;\n  errorNotifications?: boolean;\n  performanceStats?: boolean;\n  queryStats?: boolean;\n  queueStats?: boolean;\n\n  request?: request.RequestAPI<\n    request.Request,\n    request.CoreOptions,\n    request.RequiredUriUrl\n  >;\n}\n"
  },
  {
    "path": "packages/browser/src/processor/esp.ts",
    "content": "import { INoticeError, INoticeFrame } from '../notice';\n\nimport ErrorStackParser from 'error-stack-parser';\n\nconst hasConsole = typeof console === 'object' && console.warn;\n\nexport interface IStackFrame {\n  functionName?: string;\n  fileName?: string;\n  lineNumber?: number;\n  columnNumber?: number;\n}\n\nexport interface IError extends Error, IStackFrame {\n  noStack?: boolean;\n}\n\nfunction parse(err: IError): IStackFrame[] {\n  try {\n    return ErrorStackParser.parse(err);\n  } catch (parseErr) {\n    if (hasConsole && err.stack) {\n      console.warn('ErrorStackParser:', parseErr.toString(), err.stack);\n    }\n  }\n\n  if (err.fileName) {\n    return [err];\n  }\n\n  return [];\n}\n\nexport function espProcessor(err: IError): INoticeError {\n  let backtrace: INoticeFrame[] = [];\n\n  if (err.noStack) {\n    backtrace.push({\n      function: err.functionName || '',\n      file: err.fileName || '',\n      line: err.lineNumber || 0,\n      column: err.columnNumber || 0,\n    });\n  } else {\n    let frames = parse(err);\n    if (frames.length === 0) {\n      try {\n        throw new Error('fake');\n      } catch (fakeErr) {\n        frames = parse(fakeErr);\n        frames.shift();\n        frames.shift();\n      }\n    }\n\n    for (let frame of frames) {\n      backtrace.push({\n        function: frame.functionName || '',\n        file: frame.fileName || '',\n        line: frame.lineNumber || 0,\n        column: frame.columnNumber || 0,\n      });\n    }\n  }\n\n  let type: string = err.name ? err.name : '';\n  let msg: string = err.message ? String(err.message) : String(err);\n\n  return {\n    type,\n    message: msg,\n    backtrace,\n  };\n}\n"
  },
  {
    "path": "packages/browser/src/processor/processor.ts",
    "content": "import { INoticeError } from '../notice';\n\nexport type Processor = (err: Error) => INoticeError;\n"
  },
  {
    "path": "packages/browser/src/queries.ts",
    "content": "import { makeRequester, Requester } from './http_req';\nimport { IOptions } from './options';\nimport { hasTdigest, TDigestStat } from './tdshared';\n\nconst FLUSH_INTERVAL = 15000; // 15 seconds\n\ninterface IQueryKey {\n  method: string;\n  route: string;\n  query: string;\n  func: string;\n  file: string;\n  line: number;\n  time: Date;\n}\n\nexport class QueryInfo {\n  method = '';\n  route = '';\n  query = '';\n  func = '';\n  file = '';\n  line = 0;\n  startTime = new Date();\n  endTime: Date;\n\n  constructor(query = '') {\n    this.query = query;\n  }\n\n  _duration(): number {\n    if (!this.endTime) {\n      this.endTime = new Date();\n    }\n    return this.endTime.getTime() - this.startTime.getTime();\n  }\n}\n\nexport class QueriesStats {\n  _opt: IOptions;\n  _url: string;\n  _requester: Requester;\n\n  _m: { [key: string]: TDigestStat } = {};\n  _timer;\n\n  constructor(opt: IOptions) {\n    this._opt = opt;\n    this._url = `${opt.host}/api/v5/projects/${opt.projectId}/queries-stats?key=${opt.projectKey}`;\n    this._requester = makeRequester(opt);\n  }\n\n  start(query = ''): QueryInfo {\n    return new QueryInfo(query);\n  }\n\n  notify(q: QueryInfo): void {\n    if (!hasTdigest) {\n      return;\n    }\n\n    if (!this._opt.performanceStats) {\n      return;\n    }\n    if (!this._opt.queryStats) {\n      return;\n    }\n\n    let ms = q._duration();\n\n    const minute = 60 * 1000;\n    let startTime = new Date(\n      Math.floor(q.startTime.getTime() / minute) * minute\n    );\n\n    let key: IQueryKey = {\n      method: q.method,\n      route: q.route,\n      query: q.query,\n      func: q.func,\n      file: q.file,\n      line: q.line,\n      time: startTime,\n    };\n    let keyStr = JSON.stringify(key);\n\n    let stat = this._m[keyStr];\n    if (!stat) {\n      stat = new TDigestStat();\n      this._m[keyStr] = stat;\n    }\n\n    stat.add(ms);\n\n    if (this._timer) {\n      return;\n    }\n    this._timer = setTimeout(() => {\n      this._flush();\n    }, FLUSH_INTERVAL);\n  }\n\n  _flush(): void {\n    let queries = [];\n    for (let keyStr in this._m) {\n      if (!this._m.hasOwnProperty(keyStr)) {\n        continue;\n      }\n\n      let key: IQueryKey = JSON.parse(keyStr);\n      let v = {\n        ...key,\n        ...this._m[keyStr].toJSON(),\n      };\n\n      queries.push(v);\n    }\n\n    this._m = {};\n    this._timer = null;\n\n    let outJSON = JSON.stringify({\n      environment: this._opt.environment,\n      queries,\n    });\n    let req = {\n      method: 'POST',\n      url: this._url,\n      body: outJSON,\n    };\n    this._requester(req)\n      .then((_resp) => {\n        // nothing\n      })\n      .catch((err) => {\n        if (console.error) {\n          console.error('can not report queries stats', err);\n        }\n      });\n  }\n}\n"
  },
  {
    "path": "packages/browser/src/queues.ts",
    "content": "import { makeRequester, Requester } from './http_req';\nimport { BaseMetric } from './metrics';\nimport { IOptions } from './options';\nimport { hasTdigest, TDigestStatGroups } from './tdshared';\n\nconst FLUSH_INTERVAL = 15000; // 15 seconds\n\ninterface IQueueKey {\n  queue: string;\n  time: Date;\n}\n\nexport class QueueMetric extends BaseMetric {\n  queue: string;\n\n  constructor(queue: string) {\n    super();\n    this.queue = queue;\n    this.startTime = new Date();\n  }\n}\n\nexport class QueuesStats {\n  _opt: IOptions;\n  _url: string;\n  _requester: Requester;\n\n  _m: { [key: string]: TDigestStatGroups } = {};\n  _timer;\n\n  constructor(opt: IOptions) {\n    this._opt = opt;\n    this._url = `${opt.host}/api/v5/projects/${opt.projectId}/queues-stats?key=${opt.projectKey}`;\n    this._requester = makeRequester(opt);\n  }\n\n  notify(q: QueueMetric): void {\n    if (!hasTdigest) {\n      return;\n    }\n\n    if (!this._opt.performanceStats) {\n      return;\n    }\n    if (!this._opt.queueStats) {\n      return;\n    }\n\n    let ms = q._duration();\n    if (ms === 0) {\n      ms = 0.00001;\n    }\n\n    const minute = 60 * 1000;\n    let startTime = new Date(\n      Math.floor(q.startTime.getTime() / minute) * minute\n    );\n\n    let key: IQueueKey = {\n      queue: q.queue,\n      time: startTime,\n    };\n    let keyStr = JSON.stringify(key);\n\n    let stat = this._m[keyStr];\n    if (!stat) {\n      stat = new TDigestStatGroups();\n      this._m[keyStr] = stat;\n    }\n\n    stat.addGroups(ms, q._groups);\n\n    if (this._timer) {\n      return;\n    }\n    this._timer = setTimeout(() => {\n      this._flush();\n    }, FLUSH_INTERVAL);\n  }\n\n  _flush(): void {\n    let queues = [];\n    for (let keyStr in this._m) {\n      if (!this._m.hasOwnProperty(keyStr)) {\n        continue;\n      }\n\n      let key: IQueueKey = JSON.parse(keyStr);\n      let v = {\n        ...key,\n        ...this._m[keyStr].toJSON(),\n      };\n\n      queues.push(v);\n    }\n\n    this._m = {};\n    this._timer = null;\n\n    let outJSON = JSON.stringify({\n      environment: this._opt.environment,\n      queues,\n    });\n    let req = {\n      method: 'POST',\n      url: this._url,\n      body: outJSON,\n    };\n    this._requester(req)\n      .then((_resp) => {\n        // nothing\n      })\n      .catch((err) => {\n        if (console.error) {\n          console.error('can not report queues breakdowns', err);\n        }\n      });\n  }\n}\n"
  },
  {
    "path": "packages/browser/src/remote_settings.ts",
    "content": "import { makeRequester, Requester } from './http_req';\nimport { IOptions } from './options';\nimport { NOTIFIER_NAME, NOTIFIER_VERSION } from './version';\n\n// API version to poll.\nconst API_VER = '2020-06-18';\n\n// How frequently we should poll the config API.\nconst DEFAULT_INTERVAL = 600000; // 10 minutes\n\nconst NOTIFIER_INFO = {\n  notifier_name: NOTIFIER_NAME,\n  notifier_version: NOTIFIER_VERSION,\n  os:\n    typeof window !== 'undefined' &&\n    window.navigator &&\n    window.navigator.userAgent\n      ? window.navigator.userAgent\n      : undefined,\n  language: 'JavaScript',\n};\n\n// Remote config settings.\nconst ERROR_SETTING = 'errors';\nconst APM_SETTING = 'apm';\n\ninterface IRemoteConfig {\n  project_id: number;\n  updated_at: number;\n  poll_sec: number;\n  config_route: string;\n  settings: IRemoteConfigSetting[];\n}\n\ninterface IRemoteConfigSetting {\n  name: string;\n  enabled: boolean;\n  endpoint: string;\n}\n\ntype Entries<T> = {\n  [K in keyof T]: [K, T[K]];\n}[keyof T][];\n\nexport class RemoteSettings {\n  _opt: IOptions;\n  _requester: Requester;\n  _data: SettingsData;\n  _origErrorNotifications: boolean;\n  _origPerformanceStats: boolean;\n\n  constructor(opt: IOptions) {\n    this._opt = opt;\n    this._requester = makeRequester(opt);\n\n    this._data = new SettingsData(opt.projectId, {\n      project_id: null,\n      poll_sec: 0,\n      updated_at: 0,\n      config_route: '',\n      settings: [],\n    });\n\n    this._origErrorNotifications = opt.errorNotifications;\n    this._origPerformanceStats = opt.performanceStats;\n  }\n\n  poll(): any {\n    // First request is immediate. When it's done, we cancel it since we want to\n    // change interval time to the default value.\n    const pollerId = setInterval(() => {\n      this._doRequest();\n      clearInterval(pollerId);\n    }, 0);\n\n    // Second fetch is what always runs in background.\n    return setInterval(this._doRequest.bind(this), DEFAULT_INTERVAL);\n  }\n\n  _doRequest(): void {\n    this._requester(this._requestParams(this._opt))\n      .then((resp) => {\n        this._data.merge(resp.json);\n\n        this._opt.host = this._data.errorHost();\n        this._opt.apmHost = this._data.apmHost();\n\n        this._processErrorNotifications(this._data);\n        this._processPerformanceStats(this._data);\n      })\n      .catch((_) => {\n        return;\n      });\n  }\n\n  _requestParams(opt: IOptions): any {\n    return {\n      method: 'GET',\n      url: this._pollUrl(opt),\n      headers: {\n        Accept: 'application/json',\n        'Cache-Control': 'no-cache,no-store',\n      },\n    };\n  }\n\n  _pollUrl(opt: IOptions): string {\n    const url = this._data.configRoute(opt.remoteConfigHost);\n    let queryParams = '?';\n\n    for (const [key, value] of this._entries(NOTIFIER_INFO)) {\n      queryParams += `&${encodeURIComponent(key)}=${encodeURIComponent(value)}`;\n    }\n\n    return url + queryParams;\n  }\n\n  _processErrorNotifications(data: SettingsData): void {\n    if (!this._origErrorNotifications) {\n      return;\n    }\n    this._opt.errorNotifications = data.errorNotifications();\n  }\n\n  _processPerformanceStats(data: SettingsData): void {\n    if (!this._origPerformanceStats) {\n      return;\n    }\n    this._opt.performanceStats = data.performanceStats();\n  }\n\n  // Polyfill from:\n  // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries#polyfill\n  _entries<T>(obj: T): Entries<T> {\n    const ownProps = Object.keys(obj);\n    let i = ownProps.length;\n    const resArray = new Array(i);\n\n    while (i--) resArray[i] = [ownProps[i], obj[ownProps[i]]];\n\n    return resArray;\n  }\n}\n\nexport class SettingsData {\n  _projectId: number;\n  _data: IRemoteConfig;\n\n  constructor(projectId: number, data: IRemoteConfig) {\n    this._projectId = projectId;\n    this._data = data;\n  }\n\n  merge(other: IRemoteConfig) {\n    this._data = { ...this._data, ...other };\n  }\n\n  configRoute(remoteConfigHost: string): string {\n    const host = remoteConfigHost.replace(/\\/$/, '');\n    const configRoute = this._data.config_route;\n\n    if (\n      configRoute === null ||\n      configRoute === undefined ||\n      configRoute === ''\n    ) {\n      return `${host}/${API_VER}/config/${this._projectId}/config.json`;\n    } else {\n      return `${host}/${configRoute}`;\n    }\n  }\n\n  errorNotifications(): boolean {\n    const s = this._findSetting(ERROR_SETTING);\n    if (s === null) {\n      return true;\n    }\n\n    return s.enabled;\n  }\n\n  performanceStats(): boolean {\n    const s = this._findSetting(APM_SETTING);\n    if (s === null) {\n      return true;\n    }\n\n    return s.enabled;\n  }\n\n  errorHost(): string {\n    const s = this._findSetting(ERROR_SETTING);\n    if (s === null) {\n      return null;\n    }\n\n    return s.endpoint;\n  }\n\n  apmHost(): string {\n    const s = this._findSetting(APM_SETTING);\n    if (s === null) {\n      return null;\n    }\n\n    return s.endpoint;\n  }\n\n  _findSetting(name: string): IRemoteConfigSetting {\n    const settings = this._data.settings;\n    if (settings === null || settings === undefined) {\n      return null;\n    }\n\n    const setting = settings.find((s) => {\n      return s.name === name;\n    });\n\n    if (setting === undefined) {\n      return null;\n    }\n\n    return setting;\n  }\n}\n"
  },
  {
    "path": "packages/browser/src/routes.ts",
    "content": "import { makeRequester, Requester } from './http_req';\nimport { BaseMetric } from './metrics';\nimport { IOptions } from './options';\nimport { hasTdigest, TDigestStat, TDigestStatGroups } from './tdshared';\n\nconst FLUSH_INTERVAL = 15000; // 15 seconds\n\ninterface IRouteKey {\n  method: string;\n  route: string;\n  statusCode: number;\n  time: Date;\n}\n\ninterface IBreakdownKey {\n  method: string;\n  route: string;\n  responseType: string;\n  time: Date;\n}\n\nexport class RouteMetric extends BaseMetric {\n  method: string;\n  route: string;\n  statusCode: number;\n  contentType: string;\n\n  constructor(method = '', route = '', statusCode = 0, contentType = '') {\n    super();\n    this.method = method;\n    this.route = route;\n    this.statusCode = statusCode;\n    this.contentType = contentType;\n    this.startTime = new Date();\n  }\n}\n\nexport class RoutesStats {\n  _opt: IOptions;\n  _url: string;\n  _requester: Requester;\n\n  _m: { [key: string]: TDigestStat } = {};\n  _timer;\n\n  constructor(opt: IOptions) {\n    this._opt = opt;\n    this._url = `${opt.host}/api/v5/projects/${opt.projectId}/routes-stats?key=${opt.projectKey}`;\n    this._requester = makeRequester(opt);\n  }\n\n  notify(req: RouteMetric): void {\n    if (!hasTdigest) {\n      return;\n    }\n\n    if (!this._opt.performanceStats) {\n      return;\n    }\n\n    let ms = req._duration();\n\n    const minute = 60 * 1000;\n    let startTime = new Date(\n      Math.floor(req.startTime.getTime() / minute) * minute\n    );\n\n    let key: IRouteKey = {\n      method: req.method,\n      route: req.route,\n      statusCode: req.statusCode,\n      time: startTime,\n    };\n    let keyStr = JSON.stringify(key);\n\n    let stat = this._m[keyStr];\n    if (!stat) {\n      stat = new TDigestStat();\n      this._m[keyStr] = stat;\n    }\n\n    stat.add(ms);\n\n    if (this._timer) {\n      return;\n    }\n    this._timer = setTimeout(() => {\n      this._flush();\n    }, FLUSH_INTERVAL);\n  }\n\n  _flush(): void {\n    let routes = [];\n    for (let keyStr in this._m) {\n      if (!this._m.hasOwnProperty(keyStr)) {\n        continue;\n      }\n\n      let key: IRouteKey = JSON.parse(keyStr);\n      let v = {\n        ...key,\n        ...this._m[keyStr].toJSON(),\n      };\n\n      routes.push(v);\n    }\n\n    this._m = {};\n    this._timer = null;\n\n    let outJSON = JSON.stringify({\n      environment: this._opt.environment,\n      routes,\n    });\n    let req = {\n      method: 'POST',\n      url: this._url,\n      body: outJSON,\n    };\n    this._requester(req)\n      .then((_resp) => {\n        // nothing\n      })\n      .catch((err) => {\n        if (console.error) {\n          console.error('can not report routes stats', err);\n        }\n      });\n  }\n}\n\nexport class RoutesBreakdowns {\n  _opt: IOptions;\n  _url: string;\n  _requester: Requester;\n\n  _m: { [key: string]: TDigestStatGroups } = {};\n  _timer;\n\n  constructor(opt: IOptions) {\n    this._opt = opt;\n    this._url = `${opt.host}/api/v5/projects/${opt.projectId}/routes-breakdowns?key=${opt.projectKey}`;\n    this._requester = makeRequester(opt);\n  }\n\n  notify(req: RouteMetric): void {\n    if (!hasTdigest) {\n      return;\n    }\n\n    if (!this._opt.performanceStats) {\n      return;\n    }\n\n    if (\n      req.statusCode < 200 ||\n      (req.statusCode >= 300 && req.statusCode < 400) ||\n      req.statusCode === 404 ||\n      Object.keys(req._groups).length === 0\n    ) {\n      return;\n    }\n\n    let ms = req._duration();\n    if (ms === 0) {\n      ms = 0.00001;\n    }\n\n    const minute = 60 * 1000;\n    let startTime = new Date(\n      Math.floor(req.startTime.getTime() / minute) * minute\n    );\n\n    let key: IBreakdownKey = {\n      method: req.method,\n      route: req.route,\n      responseType: this._responseType(req),\n      time: startTime,\n    };\n    let keyStr = JSON.stringify(key);\n\n    let stat = this._m[keyStr];\n    if (!stat) {\n      stat = new TDigestStatGroups();\n      this._m[keyStr] = stat;\n    }\n\n    stat.addGroups(ms, req._groups);\n\n    if (this._timer) {\n      return;\n    }\n    this._timer = setTimeout(() => {\n      this._flush();\n    }, FLUSH_INTERVAL);\n  }\n\n  _flush(): void {\n    let routes = [];\n    for (let keyStr in this._m) {\n      if (!this._m.hasOwnProperty(keyStr)) {\n        continue;\n      }\n\n      let key: IBreakdownKey = JSON.parse(keyStr);\n      let v = {\n        ...key,\n        ...this._m[keyStr].toJSON(),\n      };\n\n      routes.push(v);\n    }\n\n    this._m = {};\n    this._timer = null;\n\n    let outJSON = JSON.stringify({\n      environment: this._opt.environment,\n      routes,\n    });\n    let req = {\n      method: 'POST',\n      url: this._url,\n      body: outJSON,\n    };\n    this._requester(req)\n      .then((_resp) => {\n        // nothing\n      })\n      .catch((err) => {\n        if (console.error) {\n          console.error('can not report routes breakdowns', err);\n        }\n      });\n  }\n\n  _responseType(req: RouteMetric): string {\n    if (req.statusCode >= 500) {\n      return '5xx';\n    }\n    if (req.statusCode >= 400) {\n      return '4xx';\n    }\n    if (!req.contentType) {\n      return '';\n    }\n    const s = req.contentType.split(';')[0].split('/');\n    return s[s.length - 1];\n  }\n}\n"
  },
  {
    "path": "packages/browser/src/scope.ts",
    "content": "import { IMetric, NoopMetric } from './metrics';\n\ninterface IHistoryRecord {\n  type: string;\n  date?: Date;\n  [key: string]: any;\n}\n\ninterface IMap {\n  [key: string]: any;\n}\n\nexport class Scope {\n  _noopMetric = new NoopMetric();\n  _routeMetric: IMetric;\n  _queueMetric: IMetric;\n\n  _context: IMap = {};\n\n  _historyMaxLen = 20;\n  _history: IHistoryRecord[] = [];\n  _lastRecord: IHistoryRecord;\n\n  clone(): Scope {\n    const clone = new Scope();\n    clone._context = { ...this._context };\n    clone._history = this._history.slice();\n    return clone;\n  }\n\n  setContext(context: IMap) {\n    this._context = { ...this._context, ...context };\n  }\n\n  context(): IMap {\n    const ctx = { ...this._context };\n    if (this._history.length > 0) {\n      ctx.history = this._history.slice();\n    }\n    return ctx;\n  }\n\n  pushHistory(state: IHistoryRecord): void {\n    if (this._isDupState(state)) {\n      if (this._lastRecord.num) {\n        this._lastRecord.num++;\n      } else {\n        this._lastRecord.num = 2;\n      }\n      return;\n    }\n\n    if (!state.date) {\n      state.date = new Date();\n    }\n    this._history.push(state);\n    this._lastRecord = state;\n\n    if (this._history.length > this._historyMaxLen) {\n      this._history = this._history.slice(-this._historyMaxLen);\n    }\n  }\n\n  private _isDupState(state): boolean {\n    if (!this._lastRecord) {\n      return false;\n    }\n    for (let key in state) {\n      if (!state.hasOwnProperty(key) || key === 'date') {\n        continue;\n      }\n      if (state[key] !== this._lastRecord[key]) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  routeMetric(): IMetric {\n    return this._routeMetric || this._noopMetric;\n  }\n\n  setRouteMetric(metric: IMetric) {\n    this._routeMetric = metric;\n  }\n\n  queueMetric(): IMetric {\n    return this._queueMetric || this._noopMetric;\n  }\n\n  setQueueMetric(metric: IMetric) {\n    this._queueMetric = metric;\n  }\n}\n"
  },
  {
    "path": "packages/browser/src/tdshared.ts",
    "content": "let tdigest;\nexport let hasTdigest = false;\n\ntry {\n  tdigest = require('tdigest');\n  hasTdigest = true;\n} catch (err) {}\n\ninterface ICentroid {\n  mean: number;\n  n: number;\n}\n\ninterface ICentroids {\n  each(fn: (c: ICentroid) => void): void;\n}\n\ninterface ITDigest {\n  centroids: ICentroids;\n\n  push(x: number);\n  compress();\n}\n\ninterface ITDigestCentroids {\n  mean: number[];\n  count: number[];\n}\n\nexport class TDigestStat {\n  count = 0;\n  sum = 0;\n  sumsq = 0;\n  _td = new tdigest.Digest();\n\n  add(ms: number) {\n    if (ms === 0) {\n      ms = 0.00001;\n    }\n    this.count += 1;\n    this.sum += ms;\n    this.sumsq += ms * ms;\n    if (this._td) {\n      this._td.push(ms);\n    }\n  }\n\n  toJSON() {\n    return {\n      count: this.count,\n      sum: this.sum,\n      sumsq: this.sumsq,\n      tdigestCentroids: tdigestCentroids(this._td),\n    };\n  }\n}\n\nexport class TDigestStatGroups extends TDigestStat {\n  groups: { [key: string]: TDigestStat } = {};\n\n  addGroups(totalMs: number, groups: { [key: string]: number }) {\n    this.add(totalMs);\n    for (const name in groups) {\n      if (groups.hasOwnProperty(name)) {\n        this.addGroup(name, groups[name]);\n      }\n    }\n  }\n\n  addGroup(name: string, ms: number) {\n    let stat = this.groups[name];\n    if (!stat) {\n      stat = new TDigestStat();\n      this.groups[name] = stat;\n    }\n    stat.add(ms);\n  }\n\n  toJSON() {\n    return {\n      count: this.count,\n      sum: this.sum,\n      sumsq: this.sumsq,\n      tdigestCentroids: tdigestCentroids(this._td),\n      groups: this.groups,\n    };\n  }\n}\n\nfunction tdigestCentroids(td: ITDigest): ITDigestCentroids {\n  let means: number[] = [];\n  let counts: number[] = [];\n  td.centroids.each((c: ICentroid) => {\n    means.push(c.mean);\n    counts.push(c.n);\n  });\n  return {\n    mean: means,\n    count: counts,\n  };\n}\n"
  },
  {
    "path": "packages/browser/src/version.ts",
    "content": "export const NOTIFIER_NAME = 'airbrake-js/browser';\nexport const NOTIFIER_VERSION = '2.1.9';\nexport const NOTIFIER_URL =\n  'https://github.com/airbrake/airbrake-js/tree/master/packages/browser';\n"
  },
  {
    "path": "packages/browser/tests/client.test.js",
    "content": "import { Notifier } from '../src/notifier';\n\ndescribe('Notifier config', () => {\n  const reporter = jest.fn(() => Promise.resolve({ errors: [] }));\n  const err = new Error('test');\n  let client;\n\n  test('throws when projectId or projectKey are missing', () => {\n    expect(() => {\n      new Notifier({});\n    }).toThrow('airbrake: projectId and projectKey are required');\n  });\n\n  test('calls a reporter', () => {\n    client = new Notifier({\n      projectId: 1,\n      projectKey: 'abc',\n      reporter,\n      remoteConfig: false,\n    });\n    client.notify(err);\n\n    expect(reporter.mock.calls.length).toBe(1);\n  });\n\n  test('supports environment', () => {\n    client = new Notifier({\n      projectId: 1,\n      projectKey: 'abc',\n      reporter,\n      environment: 'production',\n      remoteConfig: false,\n    });\n\n    client.notify(err);\n\n    expect(reporter.mock.calls.length).toBe(1);\n    let notice = reporter.mock.calls[0][0];\n    expect(notice.context.environment).toBe('production');\n  });\n\n  describe('keysBlocklist', () => {\n    function test(keysBlocklist) {\n      client = new Notifier({\n        projectId: 1,\n        projectKey: 'abc',\n        reporter,\n        keysBlocklist,\n        remoteConfig: false,\n      });\n\n      client.notify({\n        error: err,\n        params: {\n          key1: 'value1',\n          key2: 'value2',\n          key3: { key1: 'value1' },\n        },\n      });\n\n      expect(reporter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.params).toStrictEqual({\n        key1: '[Filtered]',\n        key2: 'value2',\n        key3: { key1: '[Filtered]' },\n      });\n    }\n\n    it('supports exact match', () => {\n      test(['key1']);\n    });\n\n    it('supports regexp match', () => {\n      test([/key1/]);\n    });\n  });\n});\n\ndescribe('Notifier', () => {\n  let reporter;\n  let client;\n  let theErr = new Error('test');\n\n  beforeEach(() => {\n    reporter = jest.fn(() => {\n      return Promise.resolve({ id: 1 });\n    });\n    client = new Notifier({\n      projectId: 1,\n      projectKey: 'abc',\n      reporter,\n      remoteConfig: false,\n    });\n  });\n\n  describe('filter', () => {\n    it('returns null to ignore notice', () => {\n      let filter = jest.fn((_) => null);\n      client.addFilter(filter);\n\n      client.notify({});\n\n      expect(filter.mock.calls.length).toBe(1);\n      expect(reporter.mock.calls.length).toBe(0);\n    });\n\n    it('returns notice to keep it', () => {\n      let filter = jest.fn((notice) => notice);\n      client.addFilter(filter);\n\n      client.notify({});\n\n      expect(filter.mock.calls.length).toBe(1);\n      expect(reporter.mock.calls.length).toBe(1);\n    });\n\n    it('returns notice to change payload', () => {\n      let filter = jest.fn((notice) => {\n        notice.context.environment = 'production';\n        return notice;\n      });\n      client.addFilter(filter);\n\n      client.notify({});\n\n      expect(filter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.context.environment).toBe('production');\n    });\n\n    it('returns new notice to change payload', () => {\n      let newNotice = { errors: [] };\n      let filter = jest.fn((_) => {\n        return newNotice;\n      });\n      client.addFilter(filter);\n\n      client.notify({});\n\n      expect(filter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      expect(notice).toEqual(newNotice);\n    });\n  });\n\n  describe('\"Uncaught ...\" error message', () => {\n    beforeEach(() => {\n      let msg =\n        'Uncaught SecurityError: Blocked a frame with origin \"https://airbrake.io\" from accessing a cross-origin frame.';\n      client.notify({ type: '', message: msg });\n    });\n\n    it('splitted into type and message', () => {\n      expect(reporter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      let err = notice.errors[0];\n      expect(err.type).toBe('SecurityError');\n      expect(err.message).toBe(\n        'Blocked a frame with origin \"https://airbrake.io\" from accessing a cross-origin frame.'\n      );\n    });\n  });\n\n  describe('Angular error message', () => {\n    beforeEach(() => {\n      let msg = `[$injector:undef] Provider '$exceptionHandler' must return a value from $get factory method.\\nhttp://errors.angularjs.org/1.4.3/$injector/undef?p0=%24exceptionHandler`;\n      client.notify({ type: 'Error', message: msg });\n    });\n\n    it('splitted into type and message', () => {\n      expect(reporter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      let err = notice.errors[0];\n      expect(err.type).toBe('$injector:undef');\n      expect(err.message).toBe(\n        `Provider '$exceptionHandler' must return a value from $get factory method.\\nhttp://errors.angularjs.org/1.4.3/$injector/undef?p0=%24exceptionHandler`\n      );\n    });\n  });\n\n  describe('severity', () => {\n    it('defaults to \"error\"', () => {\n      client.notify(theErr);\n      let reported = reporter.mock.calls[0][0];\n      expect(reported.context.severity).toBe('error');\n    });\n\n    it('can be overriden', () => {\n      let customSeverity = 'emergency';\n\n      client.addFilter((n) => {\n        n.context.severity = customSeverity;\n        return n;\n      });\n\n      client.notify(theErr);\n      let reported = reporter.mock.calls[0][0];\n      expect(reported.context.severity).toBe(customSeverity);\n    });\n  });\n\n  describe('notify', () => {\n    it('calls reporter', () => {\n      client.notify(theErr);\n      expect(reporter.mock.calls.length).toBe(1);\n    });\n\n    describe('when errorNotifications is disabled', () => {\n      beforeEach(() => {\n        client = new Notifier({\n          projectId: 1,\n          projectKey: 'abc',\n          reporter,\n          environment: 'production',\n          errorNotifications: false,\n          remoteConfig: false,\n        });\n      });\n\n      it('does not call reporter', () => {\n        client.notify(theErr);\n        expect(reporter.mock.calls.length).toBe(0);\n      });\n\n      it('returns promise and resolves it', (done) => {\n        let promise = client.notify(theErr);\n        let onResolved = jest.fn();\n        promise.then(onResolved);\n        setTimeout(() => {\n          expect(onResolved.mock.calls.length).toBe(1);\n          done();\n        }, 0);\n      });\n    });\n\n    it('returns promise and resolves it', (done) => {\n      let promise = client.notify(theErr);\n      let onResolved = jest.fn();\n      promise.then(onResolved);\n      setTimeout(() => {\n        expect(onResolved.mock.calls.length).toBe(1);\n        done();\n      }, 0);\n    });\n\n    it('does not report same error twice', (done) => {\n      client.notify(theErr);\n      expect(reporter.mock.calls.length).toBe(1);\n\n      let promise = client.notify(theErr);\n      promise.then((notice) => {\n        expect(notice.error.toString()).toBe(\n          'Error: airbrake: error is filtered'\n        );\n        done();\n      });\n    });\n\n    it('reports NaN errors', () => {\n      client.notify(NaN);\n      expect(reporter.mock.calls.length).toEqual(1);\n\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.errors[0].message).toEqual('NaN');\n    });\n\n    it('reports undefined errors', () => {\n      client.notify(undefined);\n      expect(reporter.mock.calls.length).toEqual(1);\n\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.errors[0].message).toEqual('undefined');\n    });\n\n    it('reports empty string errors', () => {\n      client.notify('');\n      expect(reporter.mock.calls.length).toEqual(1);\n\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.errors[0].message).toEqual('<empty string>');\n    });\n\n    it('reports \"false\"', () => {\n      client.notify(false);\n      expect(reporter.mock.calls.length).toEqual(1);\n\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.errors[0].message).toEqual('false');\n    });\n\n    it('reports \"null\"', () => {\n      client.notify(null);\n      expect(reporter.mock.calls.length).toEqual(1);\n\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.errors[0].message).toEqual('null');\n    });\n\n    it('reports severity', () => {\n      client.notify({ error: theErr, context: { severity: 'warning' } });\n\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.context.severity).toBe('warning');\n    });\n\n    it('reports userAgent', () => {\n      client.notify(theErr);\n\n      let notice = reporter.mock.calls[0][0];\n      expect(notice.context.userAgent).toContain('Mozilla');\n    });\n\n    it('reports text error', () => {\n      client.notify('hello');\n\n      expect(reporter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      let err = notice.errors[0];\n      expect(err.message).toBe('hello');\n      expect(err.backtrace.length).not.toBe(0);\n    });\n\n    it('ignores \"Script error\" message', () => {\n      client.notify('Script error');\n\n      expect(reporter.mock.calls.length).toBe(0);\n    });\n\n    it('ignores \"InvalidAccessError\" message', () => {\n      client.notify('InvalidAccessError');\n\n      expect(reporter.mock.calls.length).toBe(0);\n    });\n\n    it('ignores errors occurred in <anonymous> file', () => {\n      client.notify({ message: 'test', fileName: '<anonymous>' });\n\n      expect(reporter.mock.calls.length).toBe(0);\n    });\n\n    describe('custom data in the filter', () => {\n      it('reports context', () => {\n        client.addFilter((n) => {\n          n.context.context_key = '[custom_context]';\n          return n;\n        });\n        client.notify(theErr);\n\n        let reported = reporter.mock.calls[0][0];\n        expect(reported.context.context_key).toEqual('[custom_context]');\n      });\n\n      it('reports environment', () => {\n        client.addFilter((n) => {\n          n.environment.env_key = '[custom_env]';\n          return n;\n        });\n        client.notify(theErr);\n\n        let reported = reporter.mock.calls[0][0];\n        expect(reported.environment.env_key).toEqual('[custom_env]');\n      });\n\n      it('reports params', () => {\n        client.addFilter((n) => {\n          n.params.params_key = '[custom_params]';\n          return n;\n        });\n        client.notify(theErr);\n\n        let reported = reporter.mock.calls[0][0];\n        expect(reported.params.params_key).toEqual('[custom_params]');\n      });\n\n      it('reports session', () => {\n        client.addFilter((n) => {\n          n.session.session_key = '[custom_session]';\n          return n;\n        });\n        client.notify(theErr);\n\n        let reported = reporter.mock.calls[0][0];\n        expect(reported.session.session_key).toEqual('[custom_session]');\n      });\n    });\n\n    describe('wrapped error', () => {\n      it('unwraps and processes error', () => {\n        client.notify({ error: theErr });\n        expect(reporter.mock.calls.length).toBe(1);\n      });\n\n      it('reports NaN errors', () => {\n        client.notify({ error: NaN });\n        expect(reporter.mock.calls.length).toEqual(1);\n\n        let notice = reporter.mock.calls[0][0];\n        expect(notice.errors[0].message).toEqual('NaN');\n      });\n\n      it('reports undefined errors', () => {\n        client.notify({ error: undefined });\n        expect(reporter.mock.calls.length).toEqual(1);\n\n        let notice = reporter.mock.calls[0][0];\n        expect(notice.errors[0].message).toEqual('undefined');\n      });\n\n      it('reports empty string errors', () => {\n        client.notify({ error: '' });\n        expect(reporter.mock.calls.length).toEqual(1);\n\n        let notice = reporter.mock.calls[0][0];\n        expect(notice.errors[0].message).toEqual('<empty string>');\n      });\n\n      it('reports \"false\"', () => {\n        client.notify({ error: false });\n        expect(reporter.mock.calls.length).toEqual(1);\n\n        let notice = reporter.mock.calls[0][0];\n        expect(notice.errors[0].message).toEqual('false');\n      });\n\n      it('reports \"null\"', () => {\n        client.notify({ error: null });\n        expect(reporter.mock.calls.length).toEqual(1);\n\n        let notice = reporter.mock.calls[0][0];\n        expect(notice.errors[0].message).toEqual('null');\n      });\n\n      it('reports custom context', () => {\n        client.addFilter((n) => {\n          n.context.context1 = 'value1';\n          n.context.context2 = 'value2';\n          return n;\n        });\n\n        client.notify({\n          error: theErr,\n          context: {\n            context1: 'notify_value1',\n            context3: 'notify_value3',\n          },\n        });\n\n        let reported = reporter.mock.calls[0][0];\n        expect(reported.context.context1).toBe('value1');\n        expect(reported.context.context2).toBe('value2');\n        expect(reported.context.context3).toBe('notify_value3');\n      });\n\n      it('reports custom environment', () => {\n        client.addFilter((n) => {\n          n.environment.env1 = 'value1';\n          n.environment.env2 = 'value2';\n          return n;\n        });\n\n        client.notify({\n          error: theErr,\n          environment: {\n            env1: 'notify_value1',\n            env3: 'notify_value3',\n          },\n        });\n\n        let reported = reporter.mock.calls[0][0];\n        expect(reported.environment).toStrictEqual({\n          env1: 'value1',\n          env2: 'value2',\n          env3: 'notify_value3',\n        });\n      });\n\n      it('reports custom params', () => {\n        client.addFilter((n) => {\n          n.params.param1 = 'value1';\n          n.params.param2 = 'value2';\n          return n;\n        });\n\n        client.notify({\n          error: theErr,\n          params: {\n            param1: 'notify_value1',\n            param3: 'notify_value3',\n          },\n        });\n\n        let params = reporter.mock.calls[0][0].params;\n        expect(params.param1).toBe('value1');\n        expect(params.param2).toBe('value2');\n        expect(params.param3).toBe('notify_value3');\n      });\n\n      it('reports custom session', () => {\n        client.addFilter((n) => {\n          n.session.session1 = 'value1';\n          n.session.session2 = 'value2';\n          return n;\n        });\n\n        client.notify({\n          error: theErr,\n          session: {\n            session1: 'notify_value1',\n            session3: 'notify_value3',\n          },\n        });\n\n        let reported = reporter.mock.calls[0][0];\n        expect(reported.session).toStrictEqual({\n          session1: 'value1',\n          session2: 'value2',\n          session3: 'notify_value3',\n        });\n      });\n    });\n  });\n\n  describe('location', () => {\n    let notice;\n\n    beforeEach(() => {\n      client.notify(theErr);\n      expect(reporter.mock.calls.length).toBe(1);\n      notice = reporter.mock.calls[0][0];\n    });\n\n    it('reports context.url', () => {\n      expect(notice.context.url).toEqual('http://localhost/');\n    });\n\n    it('reports context.rootDirectory', () => {\n      expect(notice.context.rootDirectory).toEqual('http://localhost');\n    });\n  });\n\n  describe('wrap', () => {\n    it('does not invoke function immediately', () => {\n      let fn = jest.fn();\n      client.wrap(fn);\n      expect(fn.mock.calls.length).toBe(0);\n    });\n\n    it('creates wrapper that invokes function with passed args', () => {\n      let fn = jest.fn();\n      let wrapper = client.wrap(fn);\n      wrapper('hello', 'world');\n      expect(fn.mock.calls.length).toBe(1);\n      expect(fn.mock.calls[0]).toEqual(['hello', 'world']);\n    });\n\n    it('sets _airbrake and inner properties', () => {\n      let fn = jest.fn();\n      let wrapper = client.wrap(fn);\n      expect(wrapper._airbrake).toEqual(true);\n      expect(wrapper.inner).toEqual(fn);\n    });\n\n    it('copies function properties', () => {\n      let fn = jest.fn();\n      fn.prop = 'hello';\n      let wrapper = client.wrap(fn);\n      expect(wrapper.prop).toEqual('hello');\n    });\n\n    it('reports throwed exception', () => {\n      let spy = jest.fn();\n      client.notify = spy;\n      let fn = () => {\n        throw theErr;\n      };\n      let wrapper = client.wrap(fn);\n      try {\n        wrapper('hello', 'world');\n      } catch (_) {\n        // ignore\n      }\n\n      expect(spy.mock.calls.length).toBe(1);\n      expect(spy.mock.calls[0]).toEqual([\n        {\n          error: theErr,\n          params: { arguments: ['hello', 'world'] },\n        },\n      ]);\n    });\n\n    it('wraps arguments', () => {\n      let fn = jest.fn();\n      let wrapper = client.wrap(fn);\n      let arg1 = () => null;\n      wrapper(arg1);\n\n      expect(fn.mock.calls.length).toBe(1);\n      let arg1Wrapper = fn.mock.calls[0][0];\n      expect(arg1Wrapper._airbrake).toEqual(true);\n      expect(arg1Wrapper.inner).toEqual(arg1);\n    });\n  });\n\n  describe('call', () => {\n    it('reports throwed exception', () => {\n      let spy = jest.fn();\n      client.notify = spy;\n      let fn = () => {\n        throw theErr;\n      };\n      try {\n        client.call(fn, 'hello', 'world');\n      } catch (_) {\n        // ignore\n      }\n\n      expect(spy.mock.calls.length).toBe(1);\n      expect(spy.mock.calls[0]).toEqual([\n        {\n          error: theErr,\n          params: { arguments: ['hello', 'world'] },\n        },\n      ]);\n    });\n  });\n\n  describe('offline', () => {\n    let spy;\n\n    beforeEach(() => {\n      let event = new Event('offline');\n      window.dispatchEvent(event);\n\n      let promise = client.notify(theErr);\n      spy = jest.fn();\n      promise.then(spy);\n    });\n\n    it('causes client to not report errors', () => {\n      expect(reporter.mock.calls.length).toBe(0);\n    });\n\n    describe('online', () => {\n      beforeEach(() => {\n        let event = new Event('online');\n        window.dispatchEvent(event);\n      });\n\n      it('causes client to report queued errors', () => {\n        expect(reporter.mock.calls.length).toBe(1);\n      });\n\n      it('resolves promise', (done) => {\n        setTimeout(() => {\n          expect(spy.mock.calls.length).toBe(1);\n          done();\n        }, 0);\n      });\n    });\n  });\n\n  describe('errorNotifications', () => {\n    it('is set to true by default when it is not specified', () => {\n      client = new Notifier({\n        projectId: 1,\n        projectKey: 'abc',\n        remoteConfig: false,\n      });\n      expect(client._opt.errorNotifications).toBe(true);\n    });\n  });\n\n  describe('performanceStats', () => {\n    it('is set to true by default when it is not specified', () => {\n      client = new Notifier({\n        projectId: 1,\n        projectKey: 'abc',\n        remoteConfig: false,\n      });\n      expect(client._opt.performanceStats).toBe(true);\n    });\n  });\n\n  describe('queryStats', () => {\n    it('is set to true by default when it is not specified', () => {\n      client = new Notifier({\n        projectId: 1,\n        projectKey: 'abc',\n        remoteConfig: false,\n      });\n      expect(client._opt.queryStats).toBe(true);\n    });\n  });\n\n  describe('queueStats', () => {\n    it('is set to true by default when it is not specified', () => {\n      client = new Notifier({\n        projectId: 1,\n        projectKey: 'abc',\n        remoteConfig: false,\n      });\n      expect(client._opt.queueStats).toBe(true);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/browser/tests/historian.test.js",
    "content": "let { fetch, Request } = require('cross-fetch');\n\nwindow.fetch = fetch;\n\nimport { Notifier } from '../src/notifier';\n\nclass Location {\n  constructor(s) {\n    this.s = s;\n  }\n\n  toString() {\n    return this.s;\n  }\n}\n\ndescribe('instrumentation', () => {\n  let processor;\n  let reporter;\n  let client;\n\n  beforeEach(() => {\n    processor = jest.fn((data) => {\n      return data;\n    });\n    reporter = jest.fn(() => {\n      return Promise.resolve({ id: 1 });\n    });\n\n    jest\n      .spyOn(global.console, 'log')\n      .mockImplementation((args) => Promise.resolve(args));\n\n    client = new Notifier({\n      projectId: 1,\n      projectKey: 'abc',\n      processor,\n      reporter,\n      remoteConfig: false,\n    });\n  });\n\n  describe('location', () => {\n    beforeEach(() => {\n      let locations = ['', 'http://hello/world', 'foo', new Location('/')];\n      for (let loc of locations) {\n        try {\n          window.history.pushState(null, '', loc);\n        } catch (_) {\n          // ignore\n        }\n      }\n      client.notify(new Error('test'));\n    });\n\n    it('records browser history', () => {\n      expect(reporter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      let history = notice.context.history;\n\n      let state = history[history.length - 3];\n      delete state.date;\n      expect(state).toStrictEqual({\n        type: 'location',\n        from: '/',\n        to: '/world',\n      });\n\n      state = history[history.length - 2];\n      delete state.date;\n      expect(state).toStrictEqual({\n        type: 'location',\n        from: '/world',\n        to: '/foo',\n      });\n\n      state = history[history.length - 1];\n      delete state.date;\n      expect(state).toStrictEqual({\n        type: 'location',\n        from: '/foo',\n        to: '/',\n      });\n    });\n  });\n\n  describe('XHR', () => {\n    // TODO: use a mock instead of actually sending http requests\n    beforeEach(() => {\n      let promise = new Promise((resolve, reject) => {\n        var req = new XMLHttpRequest();\n        req.open('GET', 'https://httpbin.org/get');\n        req.onreadystatechange = () => {\n          if (req.readyState != 4) return;\n          if (req.status == 200) {\n            resolve(req.response);\n          } else {\n            reject();\n          }\n        };\n        req.send();\n      });\n\n      promise.then(() => {\n        client.notify(new Error('test'));\n      });\n      return promise;\n    });\n\n    it('records request', () => {\n      expect(reporter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      let history = notice.context.history;\n\n      let state = history[history.length - 1];\n      expect(state.type).toBe('xhr');\n      expect(state.method).toBe('GET');\n      expect(state.url).toBe('https://httpbin.org/get');\n      expect(state.statusCode).toBe(200);\n      expect(state.duration).toEqual(expect.any(Number));\n    });\n  });\n\n  describe('fetch', () => {\n    // TODO: use a mock instead of actually sending http requests\n    describe('simple fetch', () => {\n      beforeEach(() => {\n        let promise = window.fetch('https://httpbin.org/get');\n        promise.then(() => {\n          client.notify(new Error('test'));\n        });\n        return promise;\n      });\n\n      it('records request', () => {\n        expect(reporter.mock.calls.length).toBe(1);\n        let notice = reporter.mock.calls[0][0];\n        let history = notice.context.history;\n\n        let state = history[history.length - 1];\n        expect(state.type).toBe('xhr');\n        expect(state.method).toBe('GET');\n        expect(state.url).toBe('https://httpbin.org/get');\n        expect(state.statusCode).toBe(200);\n        expect(state.duration).toEqual(expect.any(Number));\n      });\n    });\n\n    describe('fetch with options', () => {\n      beforeEach(() => {\n        let promise = window.fetch('https://httpbin.org/post', {\n          method: 'POST',\n        });\n        promise.then(() => {\n          client.notify(new Error('test'));\n        });\n        return promise;\n      });\n\n      it('records request', () => {\n        expect(reporter.mock.calls.length).toBe(1);\n        let notice = reporter.mock.calls[0][0];\n        let history = notice.context.history;\n\n        let state = history[history.length - 1];\n        expect(state.type).toBe('xhr');\n        expect(state.method).toBe('POST');\n        expect(state.url).toBe('https://httpbin.org/post');\n        expect(state.statusCode).toBe(200);\n        expect(state.duration).toEqual(expect.any(Number));\n      });\n    });\n\n    describe('fetch with Request object', () => {\n      beforeEach(() => {\n        const req = new Request('https://httpbin.org/post', {\n          method: 'POST',\n          body: '{\"foo\": \"bar\"}',\n        });\n        let promise = window.fetch(req);\n        promise.then(() => {\n          client.notify(new Error('test'));\n        });\n        return promise;\n      });\n\n      it('records request', () => {\n        expect(reporter.mock.calls.length).toBe(1);\n        let notice = reporter.mock.calls[0][0];\n        let history = notice.context.history;\n\n        let state = history[history.length - 1];\n        expect(state.type).toBe('xhr');\n        expect(state.method).toBe('POST');\n        expect(state.url).toBe('https://httpbin.org/post');\n        expect(state.statusCode).toBe(200);\n        expect(state.duration).toEqual(expect.any(Number));\n      });\n    });\n  });\n\n  describe('console', () => {\n    beforeEach(() => {\n      for (let i = 0; i < 25; i++) {\n        // tslint:disable-next-line:no-console\n        console.log(i);\n      }\n      client.notify(new Error('test'));\n    });\n\n    it('records log message', () => {\n      expect(reporter.mock.calls.length).toBe(1);\n      let notice = reporter.mock.calls[0][0];\n      let history = notice.context.history;\n      expect(history).toHaveLength(20);\n\n      for (let i in history) {\n        if (!history.hasOwnProperty(i)) {\n          continue;\n        }\n        let state = history[i];\n        expect(state.type).toBe('log');\n        expect(state.severity).toBe('log');\n        expect(state.arguments).toStrictEqual([+i + 5]);\n        expect(state.date).not.toBeNull();\n      }\n    });\n  });\n});\n"
  },
  {
    "path": "packages/browser/tests/jsonify_notice.test.js",
    "content": "import { jsonifyNotice } from '../src/jsonify_notice';\n\ndescribe('jsonify_notice', () => {\n  const maxLength = 30000;\n\n  describe('when called with notice', () => {\n    let notice = {\n      params: { arguments: [] },\n      environment: { env1: 'value1' },\n      session: { session1: 'value1' },\n    };\n    let json;\n\n    beforeEach(() => {\n      json = jsonifyNotice(notice);\n    });\n\n    it('produces valid JSON', () => {\n      expect(JSON.parse(json)).toStrictEqual(notice);\n    });\n  });\n\n  describe('when called with huge notice', () => {\n    let json;\n\n    beforeEach(() => {\n      let notice = {\n        params: { arr: [] },\n      };\n      for (let i = 0; i < 100; i++) {\n        notice.params.arr.push(Array(100).join('x'));\n      }\n      json = jsonifyNotice(notice, { maxLength });\n    });\n\n    it('limits json size', () => {\n      expect(json.length).toBeLessThan(maxLength);\n    });\n  });\n\n  describe('when called with one huge string', () => {\n    let json;\n\n    beforeEach(() => {\n      let notice = {\n        params: { str: Array(100000).join('x') },\n      };\n      json = jsonifyNotice(notice, { maxLength });\n    });\n\n    it('limits json size', () => {\n      expect(json.length).toBeLessThan(maxLength);\n    });\n  });\n\n  describe('when called with huge error message', () => {\n    let json;\n\n    beforeEach(() => {\n      let notice = {\n        errors: [\n          {\n            type: Array(100000).join('x'),\n            message: Array(100000).join('x'),\n          },\n        ],\n      };\n      json = jsonifyNotice(notice, { maxLength });\n    });\n\n    it('limits json size', () => {\n      expect(json.length).toBeLessThan(maxLength);\n    });\n  });\n\n  describe('when called with huger array', () => {\n    let json;\n\n    beforeEach(() => {\n      let notice = {\n        params: { param1: Array(100000) },\n      };\n      json = jsonifyNotice(notice, { maxLength });\n    });\n\n    it('limits json size', () => {\n      expect(json.length).toBeLessThan(maxLength);\n    });\n  });\n\n  describe('when called with a blocklisted key', () => {\n    const notice = {\n      params: { name: 'I will be filtered' },\n      session: { session1: 'value1' },\n      context: { notifier: { name: 'airbrake-js' } },\n    };\n    let json;\n\n    beforeEach(() => {\n      json = jsonifyNotice(notice, { keysBlocklist: ['name'] });\n    });\n\n    it('filters out blocklisted keys', () => {\n      expect(JSON.parse(json)).toStrictEqual({\n        params: { name: '[Filtered]' },\n        session: { session1: 'value1' },\n        context: { notifier: { name: 'airbrake-js' } },\n      });\n    });\n  });\n\n  describe('keysAllowlist', () => {\n    describe('when the allowlist key is a string', () => {\n      const notice = {\n        params: { name: 'I am allowlisted', email: 'I will be filtered' },\n        session: { session1: 'I will be filtered, too' },\n        context: { notifier: { name: 'I am allowlisted' } },\n      };\n      let json;\n\n      beforeEach(() => {\n        json = jsonifyNotice(notice, { keysAllowlist: ['name'] });\n      });\n\n      it('filters out everything but allowlisted keys', () => {\n        expect(JSON.parse(json)).toStrictEqual({\n          params: { name: 'I am allowlisted', email: '[Filtered]' },\n          session: { session1: '[Filtered]' },\n          context: { notifier: { name: 'I am allowlisted' } },\n        });\n      });\n    });\n\n    describe('when the allowlist key is a regexp', () => {\n      const notice = {\n        params: { name: 'I am allowlisted', email: 'I will be filtered' },\n        session: { session1: 'I will be filtered, too' },\n        context: { notifier: { name: 'I am allowlisted' } },\n      };\n      let json;\n\n      beforeEach(() => {\n        json = jsonifyNotice(notice, { keysAllowlist: [/nam/] });\n      });\n\n      it('filters out everything but allowlisted keys', () => {\n        expect(JSON.parse(json)).toStrictEqual({\n          params: { name: 'I am allowlisted', email: '[Filtered]' },\n          session: { session1: '[Filtered]' },\n          context: { notifier: { name: 'I am allowlisted' } },\n        });\n      });\n    });\n  });\n\n  describe('when called both with a blocklist and an allowlist', () => {\n    const notice = {\n      params: { name: 'Name' },\n      session: { session1: 'value1' },\n      context: { notifier: { name: 'airbrake-js' } },\n    };\n    let json;\n\n    beforeEach(() => {\n      json = jsonifyNotice(notice, {\n        keysBlocklist: ['name'],\n        keysAllowlist: ['name'],\n      });\n    });\n\n    it('ignores the blocklist and uses the allowlist', () => {\n      expect(JSON.parse(json)).toStrictEqual({\n        params: { name: 'Name' },\n        session: { session1: '[Filtered]' },\n        context: { notifier: { name: 'airbrake-js' } },\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/browser/tests/processor/stacktracejs.test.js",
    "content": "import { INoticeError } from '../../src/notice';\nimport { espProcessor } from '../../src/processor/esp';\n\ndescribe('stacktracejs processor', () => {\n  let error;\n\n  describe('Error', () => {\n    function throwTestError() {\n      try {\n        throw new Error('BOOM');\n      } catch (err) {\n        error = espProcessor(err);\n      }\n    }\n\n    beforeEach(() => {\n      throwTestError();\n    });\n\n    it('provides type and message', () => {\n      expect(error.type).toBe('Error');\n      expect(error.message).toBe('BOOM');\n    });\n\n    it('provides backtrace', () => {\n      let backtrace = error.backtrace;\n      expect(backtrace.length).toBeGreaterThanOrEqual(5);\n\n      let frame = backtrace[0];\n      expect(frame.file).toContain('tests/processor/stacktracejs.test');\n      expect(frame.function).toBe('throwTestError');\n      expect(frame.line).toEqual(expect.any(Number));\n      expect(frame.column).toEqual(expect.any(Number));\n    });\n  });\n\n  describe('text', () => {\n    beforeEach(() => {\n      let err;\n      err = 'BOOM';\n\n      error = espProcessor(err);\n    });\n\n    it('uses text as error message', () => {\n      expect(error.type).toBe('');\n      expect(error.message).toBe('BOOM');\n    });\n\n    it('provides backtrace', () => {\n      let backtrace = error.backtrace;\n      expect(backtrace.length).toBeGreaterThanOrEqual(4);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/browser/tests/remote_settings.test.js",
    "content": "import { SettingsData } from '../src/remote_settings';\n\ndescribe('SettingsData', () => {\n  describe('merge', () => {\n    it('merges JSON with a SettingsData', () => {\n      const disabledApm = { settings: [{ name: 'apm', enabled: false }] };\n      const enabledApm = { settings: [{ name: 'apm', enabled: true }] };\n\n      const s = new SettingsData(1, disabledApm);\n      s.merge(enabledApm);\n\n      expect(s._data).toMatchObject(enabledApm);\n    });\n  });\n\n  describe('configRoute', () => {\n    describe('when config_route in JSON is null', () => {\n      it('returns the default route', () => {\n        const s = new SettingsData(1, { config_route: null });\n        expect(s.configRoute('http://example.com/')).toMatch(\n          'http://example.com/2020-06-18/config/1/config.json'\n        );\n      });\n    });\n\n    describe('when config_route in JSON is undefined', () => {\n      it('returns the default route', () => {\n        const s = new SettingsData(1, { config_route: undefined });\n        expect(s.configRoute('http://example.com/')).toMatch(\n          'http://example.com/2020-06-18/config/1/config.json'\n        );\n      });\n    });\n\n    describe('when config_route in JSON is an empty string', () => {\n      it('returns the default route', () => {\n        const s = new SettingsData(1, { config_route: '' });\n        expect(s.configRoute('http://example.com/')).toMatch(\n          'http://example.com/2020-06-18/config/1/config.json'\n        );\n      });\n    });\n\n    describe('when config_route in JSON is specified', () => {\n      it('returns the specified route', () => {\n        const s = new SettingsData(1, { config_route: 'ROUTE/cfg.json' });\n        expect(s.configRoute('http://example.com/')).toMatch(\n          'http://example.com/ROUTE/cfg.json'\n        );\n      });\n    });\n\n    describe('when the given host does not contain an ending slash', () => {\n      it('returns the specified route', () => {\n        const s = new SettingsData(1, { config_route: 'ROUTE/cfg.json' });\n        expect(s.configRoute('http://example.com')).toMatch(\n          'http://example.com/ROUTE/cfg.json'\n        );\n      });\n    });\n  });\n\n  describe('errorNotifications', () => {\n    describe('when the \"errors\" setting exists', () => {\n      describe('and when it is enabled', () => {\n        it('returns true', () => {\n          const s = new SettingsData(1, {\n            settings: [{ name: 'errors', enabled: true }],\n          });\n          expect(s.errorNotifications()).toBe(true);\n        });\n      });\n\n      describe('and when it is disabled', () => {\n        it('returns false', () => {\n          const s = new SettingsData(1, {\n            settings: [{ name: 'errors', enabled: false }],\n          });\n          expect(s.errorNotifications()).toBe(false);\n        });\n      });\n    });\n\n    describe('when the \"errors\" setting DOES NOT exist', () => {\n      it('returns true', () => {\n        const s = new SettingsData(1, {});\n        expect(s.errorNotifications()).toBe(true);\n      });\n    });\n  });\n\n  describe('performanceStats', () => {\n    describe('when the \"apm\" setting exists', () => {\n      describe('and when it is enabled', () => {\n        it('returns true', () => {\n          const s = new SettingsData(1, {\n            settings: [{ name: 'apm', enabled: true }],\n          });\n          expect(s.performanceStats()).toBe(true);\n        });\n      });\n\n      describe('and when it is disabled', () => {\n        it('returns false', () => {\n          const s = new SettingsData(1, {\n            settings: [{ name: 'apm', enabled: false }],\n          });\n          expect(s.performanceStats()).toBe(false);\n        });\n      });\n    });\n\n    describe('when the \"errors\" setting DOES NOT exist', () => {\n      it('returns true', () => {\n        const s = new SettingsData(1, {});\n        expect(s.performanceStats()).toBe(true);\n      });\n    });\n  });\n\n  describe('errorHost', () => {\n    describe('when the \"errors\" setting exists', () => {\n      describe('and when it has an endpoint specified', () => {\n        it('returns the endpoint', () => {\n          const s = new SettingsData(1, {\n            settings: [{ name: 'errors', endpoint: 'http://example.com' }],\n          });\n          expect(s.errorHost()).toMatch('http://example.com');\n        });\n      });\n\n      describe('and when it has null endpoint', () => {\n        it('returns null', () => {\n          const s = new SettingsData(1, {\n            settings: [{ name: 'errors', endpoint: null }],\n          });\n          expect(s.errorHost()).toBe(null);\n        });\n      });\n    });\n\n    describe('when the \"errors\" setting DOES NOT exist', () => {\n      it('returns null', () => {\n        const s = new SettingsData(1, {});\n        expect(s.errorHost()).toBe(null);\n      });\n    });\n  });\n\n  describe('apmHost', () => {\n    describe('when the \"apm\" setting exists', () => {\n      describe('and when it has an endpoint specified', () => {\n        it('returns the endpoint', () => {\n          const s = new SettingsData(1, {\n            settings: [{ name: 'apm', endpoint: 'http://example.com' }],\n          });\n          expect(s.apmHost()).toMatch('http://example.com');\n        });\n      });\n\n      describe('and when it has null endpoint', () => {\n        it('returns null', () => {\n          const s = new SettingsData(1, {\n            settings: [{ name: 'apm', endpoint: null }],\n          });\n          expect(s.apmHost()).toBe(null);\n        });\n      });\n    });\n\n    describe('when the \"apm\" setting DOES NOT exist', () => {\n      it('returns null', () => {\n        const s = new SettingsData(1, {});\n        expect(s.apmHost()).toBe(null);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/browser/tests/truncate.test.js",
    "content": "import { truncate } from '../src/jsonify_notice';\n\ndescribe('truncate', () => {\n  it('works', () => {\n    /* tslint:disable */\n    let tests = [\n      [undefined],\n      [null],\n      [true],\n      [false],\n      [new Boolean(true)],\n      [1],\n      [3.14],\n      [new Number(1)],\n      [Infinity],\n      [NaN],\n      [Math.LN2],\n      ['hello'],\n      [new String('hello'), 'hello'],\n      [['foo', 'bar']],\n      [{ foo: 'bar' }],\n      [new Date()],\n      [/a/],\n      [new RegExp('a')],\n      [new Error('hello'), 'Error: hello'],\n    ];\n    /* tslint:enable */\n\n   \tfor (let test of tests) {\n      let wanted = test.length >= 2 ? test[1] : test[0];\n      if (isNaN(wanted)) {\n        continue;\n      }\n      expect(truncate(test[0])).toBe(wanted);\n    }\n  });\n\n  it('omits functions in object', () => {\n    /* tslint:disable */\n    let obj = {\n      foo: 'bar',\n      fn1: Math.sin,\n      fn2: () => null,\n      fn3: new Function('x', 'y', 'return x * y'),\n    };\n    /* tslint:enable */\n\n    expect(truncate(obj)).toStrictEqual({ foo: 'bar' });\n  });\n\n  it('sets object type', () => {\n    let e = new Event('load');\n\n    let got = truncate(e);\n    expect(got.__type).toBe('Event');\n  });\n\n  describe('when called with object with circular references', () => {\n    let obj = { foo: 'bar' };\n    obj.circularRef = obj;\n    obj.circularList = [obj, obj];\n    let truncated;\n\n    beforeEach(() => {\n      truncated = truncate(obj);\n    });\n\n    it('produces object with resolved circular references', () => {\n      expect(truncated).toStrictEqual({\n        foo: 'bar',\n        circularRef: '[Circular ~]',\n        circularList: ['[Circular ~]', '[Circular ~]'],\n      });\n    });\n  });\n\n  describe('when called with object with complex circular references', () => {\n    let a = { x: 1 };\n    a.a = a;\n    let b = { x: 2 };\n    b.a = a;\n    let c = { a, b };\n\n    let obj = { list: [a, b, c] };\n    obj.obj = obj;\n\n    let truncated;\n\n    beforeEach(() => {\n      truncated = truncate(obj);\n    });\n\n    it('produces object with resolved circular references', () => {\n      expect(truncated).toStrictEqual({\n        list: [\n          {\n            x: 1,\n            a: '[Circular ~.list.0]',\n          },\n          {\n            x: 2,\n            a: '[Circular ~.list.0]',\n          },\n          {\n            a: '[Circular ~.list.0]',\n            b: '[Circular ~.list.1]',\n          },\n        ],\n        obj: '[Circular ~]',\n      });\n    });\n  });\n\n  describe('when called with deeply nested objects', () => {\n    let obj = {};\n    let tmp = obj;\n    for (let i = 0; i < 100; i++) {\n      tmp.value = i;\n      tmp.obj = {};\n      tmp = tmp.obj;\n    }\n\n    let truncated;\n\n    beforeEach(() => {\n      truncated = truncate(obj, { level: 1 });\n    });\n\n    it('produces truncated object', () => {\n      expect(truncated).toStrictEqual({\n        value: 0,\n        obj: {\n          value: 1,\n          obj: {\n            value: 2,\n            obj: {\n              value: 3,\n              obj: '[Truncated Object]',\n            },\n          },\n        },\n      });\n    });\n  });\n\n  describe('when called with object created with Object.create(null)', () => {\n    it('works', () => {\n      let obj = Object.create(null);\n      obj.foo = 'bar';\n      expect(truncate(obj)).toStrictEqual({ foo: 'bar' });\n    });\n  });\n\n  describe('keysBlocklist', () => {\n    it('filters blocklisted keys', () => {\n      let obj = {\n        params: {\n          password: '123',\n          sub: {\n            secret: '123',\n          },\n        },\n      };\n      let keysBlocklist = [/password/, /secret/];\n      let truncated = truncate(obj, { keysBlocklist });\n\n      expect(truncated).toStrictEqual({\n        params: {\n          password: '[Filtered]',\n          sub: { secret: '[Filtered]' },\n        },\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/browser/tsconfig.cjs.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/browser/tsconfig.esm.json",
    "content": "{\n  \"extends\": \"../../tsconfig.esm.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"esm\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/browser/tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig.cjs.json\",\n  \"compilerOptions\": {\n    \"rootDir\": \".\",\n  },\n  \"include\": [\"src\", \"test\"]\n}\n"
  },
  {
    "path": "packages/browser/tsconfig.umd.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"module\": \"ES6\",\n  }\n}\n"
  },
  {
    "path": "packages/browser/tslint.json",
    "content": "{\n  \"extends\": [\"../../tslint.json\"]\n}\n"
  },
  {
    "path": "packages/node/LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Airbrake Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "packages/node/README.md",
    "content": "<p align=\"center\">\n  <img src=\"https://airbrake-github-assets.s3.amazonaws.com/brand/airbrake-full-logo.png\" width=\"200\">\n</p>\n\n# Official Airbrake Notifier for Node.js\n\n[![Build Status](https://github.com/airbrake/airbrake-js/workflows/CI/badge.svg?branch=master)](https://github.com/airbrake/airbrake-js/actions?query=branch%3Amaster)\n[![npm version](https://img.shields.io/npm/v/@airbrake/node.svg)](https://www.npmjs.com/package/@airbrake/node)\n[![npm dm](https://img.shields.io/npm/dm/@airbrake/node.svg)](https://www.npmjs.com/package/@airbrake/node)\n[![npm dt](https://img.shields.io/npm/dt/@airbrake/node.svg)](https://www.npmjs.com/package/@airbrake/node)\n\nThe official Airbrake notifier for capturing JavaScript errors in Node.js and\nreporting them to [Airbrake](http://airbrake.io). If you're looking for\nbrowser support, there is a\n[separate package](https://github.com/airbrake/airbrake-js/tree/master/packages/browser).\n\n## Installation\n\nUsing yarn:\n\n```sh\nyarn add @airbrake/node\n```\n\nUsing npm:\n\n```sh\nnpm install @airbrake/node\n```\n\n## Basic Usage\n\nFirst, initialize the notifier with the project ID and project key taken from\n[Airbrake](https://airbrake.io). To find your `project_id` and `project_key`\nnavigate to your project's _Settings_ and copy the values from the right\nsidebar:\n\n![][project-idkey]\n\n```js\nconst { Notifier } = require('@airbrake/node');\n\nconst airbrake = new Notifier({\n  projectId: 1,\n  projectKey: 'REPLACE_ME',\n  environment: 'production',\n});\n```\n\nThen, you can send a textual message to Airbrake:\n\n```js\nlet promise = airbrake.notify(`user id=${user_id} not found`);\npromise.then((notice) => {\n  if (notice.id) {\n    console.log('notice id', notice.id);\n  } else {\n    console.log('notify failed', notice.error);\n  }\n});\n```\n\nor report errors directly:\n\n```js\ntry {\n  throw new Error('Hello from Airbrake!');\n} catch (err) {\n  airbrake.notify(err);\n}\n```\n\nAlternatively, you can wrap any code which may throw errors using the `wrap`\nmethod:\n\n```js\nlet startApp = () => {\n  throw new Error('Hello from Airbrake!');\n};\nstartApp = airbrake.wrap(startApp);\n\n// Any exceptions thrown in startApp will be reported to Airbrake.\nstartApp();\n```\n\nor use the `call` shortcut:\n\n```js\nlet startApp = () => {\n  throw new Error('Hello from Airbrake!');\n};\n\nairbrake.call(startApp);\n```\n\n## Example configurations\n\n- [Express](examples/express)\n- [Node.js](examples/nodejs)\n\n## Advanced Usage\n\n### Notice Annotations\n\nIt's possible to annotate error notices with all sorts of useful information at\nthe time they're captured by supplying it in the object being reported.\n\n```js\ntry {\n  startApp();\n} catch (err) {\n  airbrake.notify({\n    error: err,\n    context: { component: 'bootstrap' },\n    environment: { env1: 'value' },\n    params: { param1: 'value' },\n    session: { session1: 'value' },\n  });\n}\n```\n\n### Severity\n\n[Severity](https://airbrake.io/docs/airbrake-faq/what-is-severity/) allows\ncategorizing how severe an error is. By default, it's set to `error`. To\nredefine severity, simply overwrite `context/severity` of a notice object:\n\n```js\nairbrake.notify({\n  error: err,\n  context: { severity: 'warning' },\n});\n```\n\n### Filtering errors\n\nThere may be some errors thrown in your application that you're not interested\nin sending to Airbrake, such as errors thrown by 3rd-party libraries.\n\nThe Airbrake notifier makes it simple to ignore this chaff while still\nprocessing legitimate errors. Add filters to the notifier by providing filter\nfunctions to `addFilter`.\n\n`addFilter` accepts the entire\n[error notice](https://airbrake.io/docs/api/#create-notice-v3) to be sent to\nAirbrake and provides access to the `context`, `environment`, `params`,\nand `session` properties. It also includes the single-element `errors` array\nwith its `backtrace` property and associated backtrace lines.\n\nThe return value of the filter function determines whether or not the error\nnotice will be submitted.\n\n- If `null` is returned, the notice is ignored.\n- Otherwise, the returned notice will be submitted.\n\nAn error notice must pass all provided filters to be submitted.\n\nIn the following example all errors triggered by admins will be ignored:\n\n```js\nairbrake.addFilter((notice) => {\n  if (notice.params.admin) {\n    // Ignore errors from admin sessions.\n    return null;\n  }\n  return notice;\n});\n```\n\nFilters can be also used to modify notice payload, e.g. to set the environment\nand application version:\n\n```js\nairbrake.addFilter((notice) => {\n  notice.context.environment = 'production';\n  notice.context.version = '1.2.3';\n  return notice;\n});\n```\n\n### Filtering keys\n\nWith the `keysBlocklist` option, you can specify a list of keys containing\nsensitive information that must be filtered out:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  keysBlocklist: [\n    'password', // exact match\n    /secret/, // regexp match\n  ],\n});\n```\n\n### Node.js request and proxy\n\nTo use the [request](https://github.com/request/request) HTTP client, pass\nthe `request` option which accepts a request wrapper:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  request: request.defaults({ proxy: 'http://localproxy.com' }),\n});\n```\n\n### Instrumentation\n\n`@airbrake/node` attempts to automatically instrument various performance\nmetrics. You can disable that behavior using the `performanceStats` option:\n\n```js\nconst airbrake = new Notifier({\n  // ...\n  performanceStats: false,\n});\n```\n\n### Filtering performance data\n\n`addPerformanceFilter` allows for filtering performance data. Return `null` in\nthe filter to prevent that metric from being reported to Airbrake.\n\n```js\nairbrake.addPerformanceFilter((metric) => {\n  if (metric.route === '/foo') {\n    // Requests to '/foo' will not be reported\n    return null;\n  }\n  return metric;\n});\n```\n\n[project-idkey]: https://s3.amazonaws.com/airbrake-github-assets/airbrake-js/project-id-key.png\n"
  },
  {
    "path": "packages/node/babel.config.js",
    "content": "module.exports = {\n  presets: [['@babel/preset-env', { targets: { node: 'current' } }]],\n};\n"
  },
  {
    "path": "packages/node/examples/express/README.md",
    "content": "# Using Airbrake with Express.js\n\nThis example Node.js application uses Express.js and sets up Airbrake to report\nerrors and performance data. To adapt this example to your app, follow these\nsteps:\n\n#### 1. Install the package\n```shell\nnpm install @airbrake/node\n```\n\n#### 2. Include @airbrake/node and the Express.js instrumentation in your app\nInclude the required Airbrake libraries in your `app.js`\n\n```js\nconst Airbrake = require('@airbrake/node');\nconst airbrakeExpress = require('@airbrake/node/dist/instrumentation/express');\n```\n\n#### 3. Configure Airbrake with your project's credentials\n\n```js\nconst airbrake = new Airbrake.Notifier({\n  projectId: process.env.AIRBRAKE_PROJECT_ID,\n  projectKey: process.env.AIRBRAKE_PROJECT_KEY,\n});\n```\n\n#### 4. Add the Airbrake Express middleware\nThis middleware should be added before any routes are defined.\n\n```js\napp.use(airbrakeExpress.makeMiddleware(airbrake));\n```\n\n#### 5. Add the Airbrake Express error handler\nThe error handler middleware should be defined last. For more info on how this\nworks, see the official\n[Express error handling doc](http://expressjs.com/en/guide/error-handling.html).\n\n```js\napp.use(airbrakeExpress.makeErrorHandler(airbrake));\n```\n\n#### 6. Run your app\nThe last step is to run your app. To test that you've configured Airbrake\ncorrectly, you can throw an error inside any of your routes:\n```js\napp.get('/hello/:name', function hello(_req, _res) {\n  throw new Error('Hello from Airbrake!');\n});\n```\n\nAny unhandled errors that are thrown will now be reported to Airbrake. See the\n[basic usage](https://github.com/airbrake/airbrake-js/tree/master/packages/node#basic-usage)\nto learn how to manually send errors to Airbrake.\n\n\n**Note:** to see this all in action, take a look at our\n[example `app.js` file](https://github.com/airbrake/airbrake-js/blob/master/packages/node/examples/express/app.js)\nand to run the example, follow the next steps.\n\n\n# Running the example app\n\nIf you want to run this example application locally, follow these steps:\n\n#### 1. Clone the airbrake-js repo:\n```shell\ngit clone git@github.com:airbrake/airbrake-js.git\n```\n#### 2. Navigate to this directory:\n```\ncd airbrake-js/packages/node/examples/express\n```\n#### 3. Run the following commands while providing your `project ID` and `project API key`\n```shell\nnpm install\nAIRBRAKE_PROJECT_ID=your-id AIRBRAKE_PROJECT_KEY=your-key node app.js\nfirefox localhost:3000\n```\n"
  },
  {
    "path": "packages/node/examples/express/app.js",
    "content": "const express = require('express');\nconst pg = require('pg');\n\nconst Airbrake = require('@airbrake/node');\nconst airbrakeExpress = require('@airbrake/node/dist/instrumentation/express');\n\nasync function main() {\n  const airbrake = new Airbrake.Notifier({\n    projectId: process.env.AIRBRAKE_PROJECT_ID,\n    projectKey: process.env.AIRBRAKE_PROJECT_KEY,\n  });\n\n  const client = new pg.Client();\n  await client.connect();\n\n  const app = express();\n\n  // This middleware should be added before any routes are defined.\n  app.use(airbrakeExpress.makeMiddleware(airbrake));\n\n  app.get('/', async function home(req, res) {\n    const result = await client.query('SELECT $1::text as message', [\n      'Hello world!',\n    ]);\n    console.log(result.rows[0].message);\n\n    res.send('Hello World!');\n  });\n\n  app.get('/hello/:name', function hello(_req, _res) {\n    throw new Error('Hello from Airbrake!');\n  });\n\n  // Error handler middleware should be the last one.\n  // See http://expressjs.com/en/guide/error-handling.html\n  app.use(airbrakeExpress.makeErrorHandler(airbrake));\n\n  app.listen(3000, function() {\n    console.log('Example app listening on port 3000!');\n  });\n}\n\nmain();\n"
  },
  {
    "path": "packages/node/examples/express/package.json",
    "content": "{\n  \"name\": \"airbrake-example\",\n  \"dependencies\": {\n    \"@airbrake/node\": \"^2.1.9\",\n    \"express\": \"^4.17.1\",\n    \"pg\": \"^8.0.0\"\n  }\n}"
  },
  {
    "path": "packages/node/examples/nodejs/README.md",
    "content": "# Using Airbrake with Node.js\n\n#### 1. Install the package\n```shell\nnpm install @airbrake/node\n```\n\n#### 2. Include @airbrake/node in your app\nInclude the required Airbrake libraries in your `app.js`\n\n```js\nconst Airbrake = require('@airbrake/node');\n```\n\n#### 3. Configure Airbrake with your project's credentials\n\n```js\nconst airbrake = new Airbrake.Notifier({\n  projectId: process.env.AIRBRAKE_PROJECT_ID,\n  projectKey: process.env.AIRBRAKE_PROJECT_KEY,\n});\n```\n\n#### 4. Run your app\nThe last step is to run your app. To test that you've configured Airbrake\ncorrectly, you can throw an error inside any of your routes:\n```js\nconst hostname = '127.0.0.1';\nconst port = 3000;\n\nconst server = http.createServer((_req, res) => {\n  res.statusCode = 200;\n  res.setHeader('Content-Type', 'text/plain');\n  res.end('Hello World');\n  throw new Error('I am an uncaught exception');\n});\n\nserver.listen(port, hostname, () => {\n  console.log(`Server running at http://${hostname}:${port}/`);\n});\n```\n\nAny unhandled errors that are thrown will now be reported to Airbrake. See the\n[basic usage](https://github.com/airbrake/airbrake-js/tree/master/packages/node#basic-usage)\nto learn how to manually send errors to Airbrake.\n\n\n**Note:** to see this all in action, take a look at our\n[example `app.js` file](https://github.com/airbrake/airbrake-js/blob/master/packages/node/examples/nodejs/app.js)\nand to run the example, follow the next steps.\n\n\n# Running the example app\n\nIf you want to run this example application locally, follow these steps:\n\n#### 1. Clone the airbrake-js repo:\n```shell\ngit clone git@github.com:airbrake/airbrake-js.git\n```\n#### 2. Navigate to this directory:\n```\ncd airbrake-js/packages/node/examples/nodejs\n```\n#### 3. Run the following commands while providing your `project ID` and `project API key`\n```shell\nnpm install\nAIRBRAKE_PROJECT_ID=your-id AIRBRAKE_PROJECT_KEY=your-key node app.js\nfirefox localhost:3000\n```\n"
  },
  {
    "path": "packages/node/examples/nodejs/app.js",
    "content": "const http = require('http');\nconst Airbrake = require('@airbrake/node');\n\nnew Airbrake.Notifier({\n  projectId: process.env.AIRBRAKE_PROJECT_ID,\n  projectKey: process.env.AIRBRAKE_PROJECT_KEY,\n});\n\nconst hostname = '127.0.0.1';\nconst port = 3000;\n\nconst server = http.createServer((_req, res) => {\n  res.statusCode = 200;\n  res.setHeader('Content-Type', 'text/plain');\n  res.end('Hello World');\n  throw new Error('I am an uncaught exception');\n});\n\nserver.listen(port, hostname, () => {\n  console.log(`Server running at http://${hostname}:${port}/`);\n});\n"
  },
  {
    "path": "packages/node/examples/nodejs/package.json",
    "content": "{\n  \"name\": \"airbrake-example\",\n  \"dependencies\": {\n    \"@airbrake/node\": \"^2.1.9\"\n  }\n}"
  },
  {
    "path": "packages/node/jest.config.js",
    "content": "module.exports = {\n  transform: {\n    '^.+\\\\.jsx?$': 'babel-jest',\n    '^.+\\\\.tsx?$': 'ts-jest',\n  },\n  testEnvironment: 'node',\n  moduleNameMapper: {\n    '^@airbrake/(.*)$': '<rootDir>/../$1/src',\n  },\n  roots: ['tests'],\n  clearMocks: true,\n};\n"
  },
  {
    "path": "packages/node/package.json",
    "content": "{\n  \"name\": \"@airbrake/node\",\n  \"version\": \"2.1.9\",\n  \"description\": \"Official Airbrake notifier for Node.js\",\n  \"author\": \"Airbrake\",\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/airbrake/airbrake-js.git\",\n    \"directory\": \"packages/node\"\n  },\n  \"homepage\": \"https://github.com/airbrake/airbrake-js/tree/master/packages/node\",\n  \"keywords\": [\n    \"exception\",\n    \"error\",\n    \"airbrake\",\n    \"notifier\"\n  ],\n  \"engines\": {\n    \"node\": \">=10\"\n  },\n  \"dependencies\": {\n    \"@airbrake/browser\": \"^2.1.9\",\n    \"cross-fetch\": \"^3.1.5\",\n    \"error-stack-parser\": \"^2.0.4\",\n    \"tdigest\": \"^0.1.1\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.9.0\",\n    \"@babel/preset-env\": \"^7.9.0\",\n    \"babel-jest\": \"^29.3.1\",\n    \"jest\": \"^27.3.1\",\n    \"prettier\": \"^2.0.2\",\n    \"ts-jest\": \"^27.1.0\",\n    \"tslint\": \"^6.1.0\",\n    \"tslint-config-prettier\": \"^1.18.0\",\n    \"tslint-plugin-prettier\": \"^2.3.0\",\n    \"typescript\": \"^4.0.2\"\n  },\n  \"main\": \"dist/index.js\",\n  \"module\": \"esm/index.js\",\n  \"files\": [\n    \"dist/\",\n    \"esm/\",\n    \"README.md\",\n    \"LICENSE\"\n  ],\n  \"scripts\": {\n    \"build\": \"yarn build:cjs && yarn build:esm\",\n    \"build:watch\": \"concurrently 'yarn build:cjs:watch' 'yarn build:esm:watch'\",\n    \"build:cjs\": \"tsc -p tsconfig.cjs.json\",\n    \"build:cjs:watch\": \"tsc -p tsconfig.cjs.json -w --preserveWatchOutput\",\n    \"build:esm\": \"tsc -p tsconfig.esm.json\",\n    \"build:esm:watch\": \"tsc -p tsconfig.esm.json -w --preserveWatchOutput\",\n    \"clean\": \"rm -rf dist esm\",\n    \"lint\": \"tslint -p .\",\n    \"test\": \"jest\"\n  }\n}"
  },
  {
    "path": "packages/node/src/filter/node.ts",
    "content": "import { INotice } from '@airbrake/browser';\nimport { NOTIFIER_NAME, NOTIFIER_VERSION, NOTIFIER_URL } from '../version';\n\nconst os = require('os');\n\nexport function nodeFilter(notice: INotice): INotice {\n  if (notice.context.notifier) {\n    notice.context.notifier.name = NOTIFIER_NAME;\n    notice.context.notifier.version = NOTIFIER_VERSION;\n    notice.context.notifier.url = NOTIFIER_URL;\n  }\n  notice.context.os = `${os.type()}/${os.release()}`;\n  notice.context.architecture = os.arch();\n  notice.context.hostname = os.hostname();\n\n  notice.params.os = {\n    homedir: os.homedir(),\n    uptime: os.uptime(),\n    freemem: os.freemem(),\n    totalmem: os.totalmem(),\n    loadavg: os.loadavg(),\n  };\n\n  notice.context.platform = process.platform;\n  if (!notice.context.rootDirectory) {\n    notice.context.rootDirectory = process.cwd();\n  }\n\n  notice.params.process = {\n    pid: process.pid,\n    cwd: process.cwd(),\n    execPath: process.execPath,\n    argv: process.argv,\n  };\n  ['uptime', 'cpuUsage', 'memoryUsage'].map((name) => {\n    if (process[name]) {\n      notice.params.process[name] = process[name]();\n    }\n  });\n\n  return notice;\n}\n"
  },
  {
    "path": "packages/node/src/index.ts",
    "content": "export { Notifier } from './notifier';\n"
  },
  {
    "path": "packages/node/src/instrumentation/debug.ts",
    "content": "import { Notifier } from '../notifier';\n\nexport function patch(createDebug, airbrake: Notifier): void {\n  const oldInit = createDebug.init;\n  createDebug.init = function (debug) {\n    oldInit.apply(this, arguments);\n\n    const oldLog = debug.log || createDebug.log;\n    debug.log = function abCreateDebug() {\n      airbrake.scope().pushHistory({\n        type: 'log',\n        arguments,\n      });\n      return oldLog.apply(this, arguments);\n    };\n  };\n}\n"
  },
  {
    "path": "packages/node/src/instrumentation/express.ts",
    "content": "import { Notifier } from '../notifier';\n\nexport function makeMiddleware(airbrake: Notifier) {\n  return function airbrakeMiddleware(req, res, next): void {\n    const route = req.route?.path?.toString() ?? 'UNKNOWN';\n    const metric = airbrake.routes.start(req.method, route);\n    if (!metric.isRecording()) {\n      next();\n      return;\n    }\n\n    const origEnd = res.end;\n    res.end = function abEnd() {\n      metric.route = req.route?.path?.toString() ?? 'UNKNOWN';\n      metric.statusCode = res.statusCode;\n      metric.contentType = res.get('Content-Type');\n      airbrake.routes.notify(metric);\n      return origEnd.apply(this, arguments);\n    };\n\n    next();\n  };\n}\n\nexport function makeErrorHandler(airbrake: Notifier) {\n  return function airbrakeErrorHandler(err: Error, req, _res, next): void {\n    const url = req.protocol + '://' + req.headers.host + req.originalUrl;\n    const notice: any = {\n      error: err,\n      context: {\n        userAddr: req.ip,\n        userAgent: req.headers['user-agent'],\n        url,\n        httpMethod: req.method,\n        component: 'express',\n      },\n    };\n\n    if (req.route) {\n      if (req.route.path) {\n        notice.context.route = req.route.path.toString();\n      }\n      if (req.route.stack && req.route.stack.length) {\n        notice.context.action = req.route.stack[0].name;\n      }\n    }\n\n    const referer = req.headers.referer;\n    if (referer) {\n      notice.context.referer = referer;\n    }\n\n    airbrake.notify(notice);\n    next(err);\n  };\n}\n"
  },
  {
    "path": "packages/node/src/instrumentation/http.ts",
    "content": "import { Notifier } from '../notifier';\n\nconst SPAN_NAME = 'http';\n\nexport function patch(http, airbrake: Notifier): void {\n  if (http.request) {\n    http.request = wrapRequest(http.request, airbrake);\n  }\n  if (http.get) {\n    http.get = wrapRequest(http.get, airbrake);\n  }\n}\n\nexport function wrapRequest(origFn, airbrake: Notifier) {\n  return function abRequest() {\n    const metric = airbrake.scope().routeMetric();\n    metric.startSpan(SPAN_NAME);\n\n    const req = origFn.apply(this, arguments);\n    if (!metric.isRecording()) {\n      return req;\n    }\n\n    const origEmit = req.emit;\n    req.emit = function (type, _res) {\n      if (type === 'response') {\n        metric.endSpan(SPAN_NAME);\n      }\n      return origEmit.apply(this, arguments);\n    };\n\n    return req;\n  };\n}\n"
  },
  {
    "path": "packages/node/src/instrumentation/https.ts",
    "content": "import { Notifier } from '../notifier';\n\nimport { wrapRequest } from './http';\n\nexport function patch(https, airbrake: Notifier): void {\n  if (https.request) {\n    https.request = wrapRequest(https.request, airbrake);\n  }\n  if (https.get) {\n    https.get = wrapRequest(https.get, airbrake);\n  }\n}\n"
  },
  {
    "path": "packages/node/src/instrumentation/mysql.ts",
    "content": "import { QueryInfo } from '@airbrake/browser';\nimport { Notifier } from '../notifier';\n\nconst SPAN_NAME = 'sql';\n\nexport function patch(mysql, airbrake: Notifier): void {\n  mysql.createPool = wrapCreatePool(mysql.createPool, airbrake);\n\n  const origCreatePoolCluster = mysql.createPoolCluster;\n  mysql.createPoolCluster = function abCreatePoolCluster() {\n    const cluster = origCreatePoolCluster.apply(this, arguments);\n    cluster.of = wrapCreatePool(cluster.of, airbrake);\n    return cluster;\n  };\n\n  const origCreateConnection = mysql.createConnection;\n  mysql.createConnection = function abCreateConnection() {\n    const conn = origCreateConnection.apply(this, arguments);\n    wrapConnection(conn, airbrake);\n    return conn;\n  };\n}\n\nfunction wrapCreatePool(origFn, airbrake: Notifier) {\n  return function abCreatePool() {\n    const pool = origFn.apply(this, arguments);\n    pool.getConnection = wrapGetConnection(pool.getConnection, airbrake);\n    return pool;\n  };\n}\n\nfunction wrapGetConnection(origFn, airbrake: Notifier) {\n  return function abGetConnection() {\n    const cb = arguments[0];\n    if (typeof cb === 'function') {\n      arguments[0] = function abCallback(_err, conn) {\n        if (conn) {\n          wrapConnection(conn, airbrake);\n        }\n        return cb.apply(this, arguments);\n      };\n    }\n    return origFn.apply(this, arguments);\n  };\n}\n\nfunction wrapConnection(conn, airbrake: Notifier): void {\n  const origQuery = conn.query;\n  conn.query = function abQuery(sql, values, cb) {\n    let foundCallback = false;\n    function wrapCallback(callback) {\n      foundCallback = true;\n      return function abCallback() {\n        endSpan();\n        return callback.apply(this, arguments);\n      };\n    }\n\n    const metric = airbrake.scope().routeMetric();\n    if (!metric.isRecording()) {\n      return origQuery.apply(this, arguments);\n    }\n    metric.startSpan(SPAN_NAME);\n\n    let qinfo: QueryInfo;\n    const endSpan = () => {\n      metric.endSpan(SPAN_NAME);\n      if (qinfo) {\n        airbrake.queries.notify(qinfo);\n      }\n    };\n\n    let query: string;\n    switch (typeof sql) {\n      case 'string':\n        query = sql;\n        break;\n      case 'function':\n        arguments[0] = wrapCallback(sql);\n        break;\n      case 'object':\n        if (typeof sql._callback === 'function') {\n          sql._callback = wrapCallback(sql._callback);\n        }\n        query = sql.sql;\n        break;\n    }\n\n    if (query) {\n      qinfo = airbrake.queries.start(query);\n    }\n\n    if (typeof values === 'function') {\n      arguments[1] = wrapCallback(values);\n    } else if (typeof cb === 'function') {\n      arguments[2] = wrapCallback(cb);\n    }\n\n    const res = origQuery.apply(this, arguments);\n\n    if (!foundCallback && res && res.emit) {\n      const origEmit = res.emit;\n      res.emit = function abEmit(evt) {\n        switch (evt) {\n          case 'end':\n          case 'error':\n            endSpan();\n            break;\n        }\n        return origEmit.apply(this, arguments);\n      };\n    }\n\n    return res;\n  };\n}\n"
  },
  {
    "path": "packages/node/src/instrumentation/mysql2.ts",
    "content": "import { QueryInfo } from '@airbrake/browser';\nimport { Notifier } from '../notifier';\n\nconst SPAN_NAME = 'sql';\n\nexport function patch(mysql2, airbrake: Notifier): void {\n  const proto = mysql2.Connection.prototype;\n  proto.query = wrapQuery(proto.query, airbrake);\n  proto.execute = wrapQuery(proto.execute, airbrake);\n}\n\nfunction wrapQuery(origQuery, airbrake: Notifier) {\n  return function abQuery(sql, values, cb) {\n    const metric = airbrake.scope().routeMetric();\n    if (!metric.isRecording()) {\n      return origQuery.apply(this, arguments);\n    }\n    metric.startSpan(SPAN_NAME);\n\n    let qinfo: QueryInfo;\n    const endSpan = () => {\n      metric.endSpan(SPAN_NAME);\n      if (qinfo) {\n        airbrake.queries.notify(qinfo);\n      }\n    };\n\n    let foundCallback = false;\n    function wrapCallback(callback) {\n      foundCallback = true;\n      return function abCallback() {\n        endSpan();\n        return callback.apply(this, arguments);\n      };\n    }\n\n    let query: string;\n    switch (typeof sql) {\n      case 'string':\n        query = sql;\n        break;\n      case 'function':\n        arguments[0] = wrapCallback(sql);\n        break;\n      case 'object':\n        if (typeof sql.onResult === 'function') {\n          sql.onResult = wrapCallback(sql.onResult);\n        }\n        query = sql.sql;\n        break;\n    }\n\n    if (query) {\n      qinfo = airbrake.queries.start(query);\n    }\n\n    if (typeof values === 'function') {\n      arguments[1] = wrapCallback(values);\n    } else if (typeof cb === 'function') {\n      arguments[2] = wrapCallback(cb);\n    }\n\n    const res = origQuery.apply(this, arguments);\n\n    if (!foundCallback && res && res.emit) {\n      const origEmit = res.emit;\n      res.emit = function abEmit(evt) {\n        switch (evt) {\n          case 'end':\n          case 'error':\n          case 'close':\n            endSpan();\n            break;\n        }\n        return origEmit.apply(this, arguments);\n      };\n    }\n\n    return res;\n  };\n}\n"
  },
  {
    "path": "packages/node/src/instrumentation/pg.ts",
    "content": "import { QueryInfo } from '@airbrake/browser';\nimport { Notifier } from '../notifier';\n\nconst SPAN_NAME = 'sql';\n\nexport function patch(pg, airbrake: Notifier): void {\n  patchClient(pg.Client, airbrake);\n\n  const origGetter = pg.__lookupGetter__('native');\n  if (origGetter) {\n    delete pg.native;\n    pg.__defineGetter__('native', () => {\n      const native = origGetter();\n      if (native && native.Client) {\n        patchClient(native.Client, airbrake);\n      }\n      return native;\n    });\n  }\n}\n\n// tslint:disable-next-line: variable-name\nfunction patchClient(Client, airbrake: Notifier): void {\n  const origQuery = Client.prototype.query;\n  Client.prototype.query = function abQuery(sql) {\n    const metric = airbrake.scope().routeMetric();\n    if (!metric.isRecording()) {\n      return origQuery.apply(this, arguments);\n    }\n    metric.startSpan(SPAN_NAME);\n\n    if (sql && typeof sql.text === 'string') {\n      sql = sql.text;\n    }\n\n    let qinfo: QueryInfo;\n    if (typeof sql === 'string') {\n      qinfo = airbrake.queries.start(sql);\n    }\n\n    let cbIdx = arguments.length - 1;\n    let cb = arguments[cbIdx];\n    if (Array.isArray(cb)) {\n      cbIdx = cb.length - 1;\n      cb = cb[cbIdx];\n    }\n\n    const endSpan = () => {\n      metric.endSpan(SPAN_NAME);\n      if (qinfo) {\n        airbrake.queries.notify(qinfo);\n      }\n    };\n\n    if (typeof cb === 'function') {\n      arguments[cbIdx] = function abCallback() {\n        endSpan();\n        return cb.apply(this, arguments);\n      };\n      return origQuery.apply(this, arguments);\n    }\n\n    const query = origQuery.apply(this, arguments);\n\n    if (typeof query.on === 'function') {\n      query.on('end', endSpan);\n      query.on('error', endSpan);\n    } else if (\n      typeof query.then === 'function' &&\n      typeof query.catch === 'function'\n    ) {\n      query.then(endSpan).catch(endSpan);\n    }\n\n    return query;\n  };\n}\n"
  },
  {
    "path": "packages/node/src/instrumentation/redis.ts",
    "content": "import { Notifier } from '../notifier';\n\nconst SPAN_NAME = 'redis';\n\nexport function patch(redis, airbrake: Notifier): void {\n  const proto = redis.RedisClient.prototype;\n  const origSendCommand = proto.internal_send_command;\n  proto.internal_send_command = function ab_internal_send_command(cmd) {\n    const metric = airbrake.scope().routeMetric();\n    metric.startSpan(SPAN_NAME);\n    if (!metric.isRecording()) {\n      return origSendCommand.apply(this, arguments);\n    }\n\n    if (cmd && cmd.callback) {\n      const origCb = cmd.callback;\n      cmd.callback = function abCallback() {\n        metric.endSpan(SPAN_NAME);\n        return origCb.apply(this, arguments);\n      };\n    }\n\n    return origSendCommand.apply(this, arguments);\n  };\n}\n"
  },
  {
    "path": "packages/node/src/notifier.ts",
    "content": "import { BaseNotifier, INotice, IOptions } from '@airbrake/browser';\nimport { nodeFilter } from './filter/node';\nimport { Scope, ScopeManager } from './scope';\n\nexport class Notifier extends BaseNotifier {\n  _inFlight: number;\n\n  _scopeManager?: ScopeManager;\n  _mainScope?: Scope;\n\n  constructor(opt: IOptions) {\n    if (!opt.environment && process.env.NODE_ENV) {\n      opt.environment = process.env.NODE_ENV;\n    }\n    super(opt);\n\n    this.addFilter(nodeFilter);\n\n    this._inFlight = 0;\n\n    process.on('beforeExit', async () => {\n      await this.flush();\n    });\n    process.on('uncaughtException', (err) => {\n      this.notify(err).then(() => {\n        if (process.listeners('uncaughtException').length !== 1) {\n          return;\n        }\n        if (console.error) {\n          console.error('uncaught exception', err);\n        }\n        process.exit(1);\n      });\n    });\n    process.on('unhandledRejection', (reason: Error, _p) => {\n      let msg = reason.message || String(reason);\n      if (msg.indexOf && msg.indexOf('airbrake: ') === 0) {\n        return;\n      }\n\n      this.notify(reason).then(() => {\n        if (process.listeners('unhandledRejection').length !== 1) {\n          return;\n        }\n        if (console.error) {\n          console.error('unhandled rejection', reason);\n        }\n        process.exit(1);\n      });\n    });\n\n    if (opt.performanceStats) {\n      this._instrument();\n      this._scopeManager = new ScopeManager();\n    }\n\n    this._mainScope = new Scope();\n  }\n\n  scope(): Scope {\n    if (this._scopeManager) {\n      const scope = this._scopeManager.active();\n      if (scope) {\n        return scope;\n      }\n    }\n    return this._mainScope;\n  }\n\n  setActiveScope(scope: Scope) {\n    this._scopeManager.setActive(scope);\n  }\n\n  notify(err: any): Promise<INotice> {\n    this._inFlight++;\n    return super.notify(err).finally(() => {\n      this._inFlight--;\n    });\n  }\n\n  async flush(timeout = 3000): Promise<boolean> {\n    if (this._inFlight === 0 || timeout <= 0) {\n      return Promise.resolve(true);\n    }\n    return new Promise((resolve, _reject) => {\n      let interval = timeout / 100;\n      if (interval <= 0) {\n        interval = 10;\n      }\n\n      const timerID = setInterval(() => {\n        if (this._inFlight === 0) {\n          resolve(true);\n          clearInterval(timerID);\n          return;\n        }\n\n        if (timeout <= 0) {\n          resolve(false);\n          clearInterval(timerID);\n          return;\n        }\n\n        timeout -= interval;\n      }, interval);\n    });\n  }\n\n  _instrument() {\n    const mods = ['pg', 'mysql', 'mysql2', 'redis', 'http', 'https'];\n    for (let modName of mods) {\n      try {\n        const mod = require(`${modName}.js`);\n        const airbrakeMod = require(`@airbrake/node/dist/instrumentation/${modName}.js`);\n        airbrakeMod.patch(mod, this);\n      } catch (_) {}\n    }\n  }\n}\n"
  },
  {
    "path": "packages/node/src/scope.ts",
    "content": "import { Scope } from '@airbrake/browser';\nimport * as asyncHooks from 'async_hooks';\n\nexport { Scope };\n\nexport class ScopeManager {\n  _asyncHook: asyncHooks.AsyncHook;\n  _scopes: { [id: number]: Scope } = {};\n\n  constructor() {\n    this._asyncHook = asyncHooks\n      .createHook({\n        init: this._init.bind(this),\n        destroy: this._destroy.bind(this),\n        promiseResolve: this._destroy.bind(this),\n      })\n      .enable();\n  }\n\n  setActive(scope: Scope) {\n    const eid = asyncHooks.executionAsyncId();\n    this._scopes[eid] = scope;\n  }\n\n  active(): Scope {\n    const eid = asyncHooks.executionAsyncId();\n    return this._scopes[eid];\n  }\n\n  _init(aid: number) {\n    this._scopes[aid] = this._scopes[asyncHooks.executionAsyncId()];\n  }\n\n  _destroy(aid: number) {\n    delete this._scopes[aid];\n  }\n}\n"
  },
  {
    "path": "packages/node/src/version.ts",
    "content": "export const NOTIFIER_NAME = 'airbrake-js/node';\nexport const NOTIFIER_VERSION = '2.1.9';\nexport const NOTIFIER_URL =\n  'https://github.com/airbrake/airbrake-js/tree/master/packages/node';\n"
  },
  {
    "path": "packages/node/tests/notifier.test.js",
    "content": "import { Notifier } from '../src/notifier';\n\ndescribe('Notifier', () => {\n  describe('configuration', () => {\n    describe('performanceStats', () => {\n      test('is enabled by default', () => {\n        const notifier = new Notifier({\n          projectId: 1,\n          projectKey: 'key',\n          remoteConfig: false,\n        });\n        expect(notifier._opt.performanceStats).toEqual(true);\n      });\n\n      test('sets up instrumentation when enabled', () => {\n        Notifier.prototype._instrument = jest.fn();\n        const notifier = new Notifier({\n          projectId: 1,\n          projectKey: 'key',\n          remoteConfig: false,\n        });\n        expect(notifier._instrument.mock.calls.length).toEqual(1);\n      });\n\n      test('can be disabled', () => {\n        const notifier = new Notifier({\n          projectId: 1,\n          projectKey: 'key',\n          performanceStats: false,\n          remoteConfig: false,\n        });\n        expect(notifier._opt.performanceStats).toEqual(false);\n      });\n\n      test('does not set up instrumentation when disabled', () => {\n        Notifier.prototype._instrument = jest.fn();\n        const notifier = new Notifier({\n          projectId: 1,\n          projectKey: 'key',\n          performanceStats: false,\n          remoteConfig: false,\n        });\n        expect(notifier._instrument.mock.calls.length).toEqual(0);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/node/tests/routes.test.js",
    "content": "import { Notifier } from '../src/notifier';\n\ndescribe('Routes', () => {\n  const opt = {\n    projectId: 1,\n    projectKey: 'test',\n    remoteConfig: false,\n  };\n  let notifier;\n  let routes;\n  let req;\n\n  beforeEach(() => {\n    notifier = new Notifier(opt);\n    routes = notifier.routes;\n    req = routes.start('GET', '/projects/:id');\n    req.statusCode = 200;\n    req.contentType = 'application/json';\n    req.startTime = new Date(1);\n    req.endTime = new Date(1000);\n  });\n\n  it('collects metrics to report to Airbrake', () => {\n    routes.notify(req);\n    clearTimeout(routes._routes._timer);\n    clearTimeout(routes._breakdowns._timer);\n\n    let m = JSON.parse(JSON.stringify(routes._routes._m));\n    expect(m).toStrictEqual({\n      '{\"method\":\"GET\",\"route\":\"/projects/:id\",\"statusCode\":200,\"time\":\"1970-01-01T00:00:00.000Z\"}': {\n        count: 1,\n        sum: 999,\n        sumsq: 998001,\n        tdigestCentroids: { count: [1], mean: [999] },\n      },\n    });\n  });\n\n  it('does not collect metrics that are filtered', () => {\n    notifier.addPerformanceFilter(() => null);\n    routes.notify(req);\n    clearTimeout(routes._routes._timer);\n    clearTimeout(routes._breakdowns._timer);\n\n    expect(routes._routes._m).toStrictEqual({});\n  });\n});\n"
  },
  {
    "path": "packages/node/tsconfig.cjs.json",
    "content": "{\n  \"extends\": \"../../tsconfig.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"dist\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/node/tsconfig.esm.json",
    "content": "{\n  \"extends\": \"../../tsconfig.esm.json\",\n  \"compilerOptions\": {\n    \"outDir\": \"esm\"\n  },\n  \"include\": [\"src\"]\n}\n"
  },
  {
    "path": "packages/node/tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig.cjs.json\",\n  \"compilerOptions\": {\n    \"rootDir\": \".\",\n  },\n  \"include\": [\"src\", \"test\"]\n}\n"
  },
  {
    "path": "packages/node/tslint.json",
    "content": "{\n  \"extends\": [\"../../tslint.json\"]\n}\n"
  },
  {
    "path": "tsconfig.esm.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"compilerOptions\": {\n    \"module\": \"ES6\"\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"esModuleInterop\": true,\n    \"lib\": [\"ES2015\", \"DOM\"],\n    \"moduleResolution\": \"node\",\n    \"noImplicitReturns\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"preserveConstEnums\": true,\n    \"skipLibCheck\": true,\n    \"sourceMap\": true,\n    \"target\": \"ES5\"\n  }\n}\n"
  },
  {
    "path": "tslint.json",
    "content": "{\n  \"extends\": [\n    \"tslint:latest\",\n    \"tslint-config-prettier\",\n    \"tslint-plugin-prettier\"\n  ],\n  \"rules\": {\n    \"max-classes-per-file\": false,\n    \"member-access\": false,\n    \"member-ordering\": false,\n    \"no-bitwise\": false,\n    \"no-console\": [true, \"log\"],\n    \"no-empty\": false,\n    \"no-implicit-dependencies\": false,\n    \"no-shadowed-variable\": [true, { \"temporalDeadZone\": false }],\n    \"no-submodule-imports\": [true, \"promise-polyfill/src/polyfill\"],\n    \"no-var-requires\": false,\n    \"object-literal-sort-keys\": false,\n    \"prefer-const\": false,\n    \"prettier\": true,\n    \"variable-name\": [true, \"allow-leading-underscore\"]\n  }\n}\n"
  }
]