diff --git a/.craft.yml b/.craft.yml index e8bbfaf3b3b5..39a52c69437a 100644 --- a/.craft.yml +++ b/.craft.yml @@ -167,6 +167,8 @@ targets: onlyIfPresent: /^sentry-angular-ivy-\d.*\.tgz$/ 'npm:@sentry/angular': onlyIfPresent: /^sentry-angular-\d.*\.tgz$/ + 'npm:@sentry/astro': + onlyIfPresent: /^sentry-astro-\d.*\.tgz$/ 'npm:@sentry/wasm': onlyIfPresent: /^sentry-wasm-\d.*\.tgz$/ 'npm:@sentry/nextjs': diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 50793411c277..c7e01af332a2 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -30,6 +30,7 @@ body: If you're using the CDN bundles, please specify the exact bundle (e.g. `bundle.tracing.min.js`) in your SDK setup. options: + - '@sentry/astro' - '@sentry/browser' - '@sentry/angular' - '@sentry/angular-ivy' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3191f92adefb..e233d1dec828 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -848,6 +848,7 @@ jobs: 'create-next-app', 'create-remix-app', 'create-remix-app-v2', + 'debug-id-sourcemaps', 'nextjs-app-dir', 'react-create-hash-router', 'react-router-6-use-routes', diff --git a/.github/workflows/issue-package-label.yml b/.github/workflows/issue-package-label.yml index f50679e5f112..1c496c927762 100644 --- a/.github/workflows/issue-package-label.yml +++ b/.github/workflows/issue-package-label.yml @@ -29,6 +29,9 @@ jobs: # Note: Since this is handled as a regex, and JSON parse wrangles slashes /, we just use `.` instead map: | { + "@sentry.astro": { + "label": "Package: Astro" + }, "@sentry.browser": { "label": "Package: Browser" }, diff --git a/.size-limit.js b/.size-limit.js index be8fa57b4934..4bcda6b45e4a 100644 --- a/.size-limit.js +++ b/.size-limit.js @@ -27,7 +27,7 @@ module.exports = [ name: '@sentry/browser (incl. Tracing, Replay) - ES6 CDN Bundle (gzipped)', path: 'packages/browser/build/bundles/bundle.tracing.replay.min.js', gzip: true, - limit: '80 KB', + limit: '90 KB', }, { name: '@sentry/browser (incl. Tracing) - ES6 CDN Bundle (gzipped)', diff --git a/CHANGELOG.md b/CHANGELOG.md index 58bd0ab2a355..d053038e9465 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,19 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 7.74.1 + +- chore(astro): Add `astro-integration` keyword (#9265) +- fix(core): Narrow filters for health check transactions (#9257) +- fix(nextjs): Fix HMR by inserting new entrypoints at the end (#9267) +- fix(nextjs): Fix resolution of request async storage module (#9259) +- fix(node-experimental): Guard against missing `fetch` (#9275) +- fix(remix): Update `defer` injection logic. (#9242) +- fix(tracing-internal): Parameterize express middleware parameters (#8668) +- fix(utils): Move Node specific ANR impl. out of utils (#9258) + +Work in this release contributed by @LubomirIgonda1. Thank you for your contribution! + ## 7.74.0 ### Important Changes diff --git a/README.md b/README.md index 66502992eca1..2f48be33350b 100644 --- a/README.md +++ b/README.md @@ -39,11 +39,13 @@ For each major JavaScript platform, there is a specific high-level SDK that prov package. Please refer to the README and instructions of those SDKs for more detailed information: - [`@sentry/browser`](https://github.com/getsentry/sentry-javascript/tree/master/packages/browser): SDK for Browsers +- [`@sentry/bun`](https://github.com/getsentry/sentry-javascript/tree/master/packages/bun): SDK for Bun - [`@sentry/node`](https://github.com/getsentry/sentry-javascript/tree/master/packages/node): SDK for Node including integrations for Express - [`@sentry/angular`](https://github.com/getsentry/sentry-javascript/tree/master/packages/angular): Browser SDK for Angular - [`@sentry/angular-ivy`](https://github.com/getsentry/sentry-javascript/tree/master/packages/angular-ivy): Browser SDK for Angular with native support for Angular's Ivy rendering engine. +- [`@sentry/astro`](https://github.com/getsentry/sentry-javascript/tree/master/packages/astro): SDK for Astro - [`@sentry/ember`](https://github.com/getsentry/sentry-javascript/tree/master/packages/ember): Browser SDK for Ember - [`@sentry/react`](https://github.com/getsentry/sentry-javascript/tree/master/packages/react): Browser SDK for React - [`@sentry/svelte`](https://github.com/getsentry/sentry-javascript/tree/master/packages/svelte): Browser SDK for Svelte diff --git a/package.json b/package.json index b5cbf9ddc38e..364c97a53a75 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "packages/ember", "packages/eslint-config-sdk", "packages/eslint-plugin-sdk", + "packages/feedback", "packages/gatsby", "packages/hub", "packages/integrations", @@ -82,7 +83,7 @@ "@rollup/plugin-replace": "^3.0.1", "@rollup/plugin-sucrase": "^4.0.3", "@rollup/plugin-typescript": "^8.3.1", - "@size-limit/preset-small-lib": "^4.5.5", + "@size-limit/preset-small-lib": "~9.0.0", "@strictsoftware/typedoc-plugin-monorepo": "^0.3.1", "@types/chai": "^4.1.3", "@types/jest": "^27.4.1", @@ -118,7 +119,7 @@ "rollup-plugin-license": "^2.6.1", "rollup-plugin-terser": "^7.0.2", "sinon": "^7.3.2", - "size-limit": "^4.5.5", + "size-limit": "~9.0.0", "ts-jest": "^27.1.4", "ts-node": "10.9.1", "tslib": "2.4.1", diff --git a/packages/astro/package.json b/packages/astro/package.json index aac0e6a88d00..91cac9038689 100644 --- a/packages/astro/package.json +++ b/packages/astro/package.json @@ -7,6 +7,7 @@ "keywords": [ "withastro", "astro-component", + "astro-integration", "sentry", "apm" ], diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index 9c6e8c9a546a..20721d9dbcec 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -6,11 +6,11 @@ import { getEventDescription, logger, stringMatchesSomePattern } from '@sentry/u const DEFAULT_IGNORE_ERRORS = [/^Script error\.?$/, /^Javascript error: Script error\.? on line 0$/]; const DEFAULT_IGNORE_TRANSACTIONS = [ - /^.*healthcheck.*$/, - /^.*healthy.*$/, - /^.*live.*$/, - /^.*ready.*$/, - /^.*heartbeat.*$/, + /^.*\/healthcheck$/, + /^.*\/healthy$/, + /^.*\/live$/, + /^.*\/ready$/, + /^.*\/heartbeat$/, /^.*\/health$/, /^.*\/healthz$/, ]; diff --git a/packages/core/test/lib/integrations/inboundfilters.test.ts b/packages/core/test/lib/integrations/inboundfilters.test.ts index 8e42c5bc29ee..dad4606a7c80 100644 --- a/packages/core/test/lib/integrations/inboundfilters.test.ts +++ b/packages/core/test/lib/integrations/inboundfilters.test.ts @@ -428,6 +428,18 @@ describe('InboundFilters', () => { expect(eventProcessor(TRANSACTION_EVENT_HEALTH_2, {})).toBe(TRANSACTION_EVENT_HEALTH_2); expect(eventProcessor(TRANSACTION_EVENT_HEALTH_3, {})).toBe(TRANSACTION_EVENT_HEALTH_3); }); + + it.each(['/delivery', '/already', '/healthysnacks'])( + "doesn't filter out transactions that have similar names to health check ones (%s)", + transaction => { + const eventProcessor = createInboundFiltersEventProcessor(); + const evt: Event = { + transaction, + type: 'transaction', + }; + expect(eventProcessor(evt, {})).toBe(evt); + }, + ); }); describe('denyUrls/allowUrls', () => { diff --git a/packages/deno/rollup.config.js b/packages/deno/rollup.config.js index 48123037a596..236501153f8b 100644 --- a/packages/deno/rollup.config.js +++ b/packages/deno/rollup.config.js @@ -1,9 +1,12 @@ +// @ts-check import nodeResolve from '@rollup/plugin-node-resolve'; import commonjs from '@rollup/plugin-commonjs'; import sucrase from '@rollup/plugin-sucrase'; +import { defineConfig } from 'rollup'; -export default { +export default defineConfig({ input: ['src/index.ts'], + treeshake: 'smallest', output: { dir: 'build', sourcemap: true, @@ -21,4 +24,4 @@ export default { commonjs(), sucrase({ transforms: ['typescript'] }), ], -}; +}); diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/.gitignore b/packages/e2e-tests/test-applications/debug-id-sourcemaps/.gitignore new file mode 100644 index 000000000000..1521c8b7652b --- /dev/null +++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/.gitignore @@ -0,0 +1 @@ +dist diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/.npmrc b/packages/e2e-tests/test-applications/debug-id-sourcemaps/.npmrc new file mode 100644 index 000000000000..c6b3ef9b3eaa --- /dev/null +++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/.npmrc @@ -0,0 +1,2 @@ +@sentry:registry=http://localhost:4873 +@sentry-internal:registry=http://localhost:4873 diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json b/packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json new file mode 100644 index 000000000000..b261ace67fb8 --- /dev/null +++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/package.json @@ -0,0 +1,23 @@ +{ + "name": "debug-id-sourcemaps", + "version": "1.0.0", + "private": true, + "scripts": { + "build": "rollup --config rollup.config.mjs", + "test": "vitest run", + "clean": "npx rimraf node_modules,pnpm-lock.yaml", + "test:build": "pnpm install && pnpm build", + "test:assert": "pnpm test" + }, + "dependencies": { + "@sentry/node": "latest || *" + }, + "devDependencies": { + "rollup": "^4.0.2", + "vitest": "^0.34.6", + "@sentry/rollup-plugin": "2.8.0" + }, + "volta": { + "extends": "../../package.json" + } +} diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/rollup.config.mjs b/packages/e2e-tests/test-applications/debug-id-sourcemaps/rollup.config.mjs new file mode 100644 index 000000000000..47273c79ea9d --- /dev/null +++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/rollup.config.mjs @@ -0,0 +1,20 @@ +import { defineConfig } from 'rollup'; +import { sentryRollupPlugin } from '@sentry/rollup-plugin'; + +export default defineConfig({ + input: './src/app.js', + external: ['@sentry/node'], + plugins: [ + sentryRollupPlugin({ + org: process.env.E2E_TEST_SENTRY_ORG_SLUG, + project: process.env.E2E_TEST_SENTRY_TEST_PROJECT, + authToken: process.env.E2E_TEST_AUTH_TOKEN, + }), + ], + output: { + file: './dist/app.js', + compact: true, + format: 'cjs', + sourcemap: 'hidden', + }, +}); diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js b/packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js new file mode 100644 index 000000000000..68a4aae16d85 --- /dev/null +++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/src/app.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/node'; + +Sentry.init({ + environment: 'qa', // dynamic sampling bias to keep transactions + dsn: process.env.E2E_TEST_DSN, +}); + +const eventId = Sentry.captureException(new Error('Sentry Debug ID E2E Test Error')); + +process.stdout.write(eventId); diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap b/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap new file mode 100644 index 000000000000..8b157beba9da --- /dev/null +++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/__snapshots__/server.test.ts.snap @@ -0,0 +1,20 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`Find symbolicated event on sentry 1`] = ` +{ + "colno": 41, + "contextLine": "const eventId = Sentry.captureException(new Error('Sentry Debug ID E2E Test Error'));", + "lineno": 8, + "postContext": [ + "", + "process.stdout.write(eventId);", + ], + "preContext": [ + "Sentry.init({", + " environment: 'qa', // dynamic sampling bias to keep transactions", + " dsn: process.env.E2E_TEST_DSN,", + "});", + "", + ], +} +`; diff --git a/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/server.test.ts b/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/server.test.ts new file mode 100644 index 000000000000..df41adb615c8 --- /dev/null +++ b/packages/e2e-tests/test-applications/debug-id-sourcemaps/tests/server.test.ts @@ -0,0 +1,53 @@ +import { test } from 'vitest'; +import childProcess from 'child_process'; +import path from 'path'; + +const authToken = process.env.E2E_TEST_AUTH_TOKEN; +const sentryTestOrgSlug = process.env.E2E_TEST_SENTRY_ORG_SLUG; +const sentryTestProject = process.env.E2E_TEST_SENTRY_TEST_PROJECT; +const EVENT_POLLING_TIMEOUT = 30_000; + +test( + 'Find symbolicated event on sentry', + async ({ expect }) => { + const eventId = childProcess.execSync(`node ${path.join(__dirname, '..', 'dist', 'app.js')}`, { + encoding: 'utf-8', + }); + + console.log(`Polling for error eventId: ${eventId}`); + + let timedOut = false; + setTimeout(() => { + timedOut = true; + }, EVENT_POLLING_TIMEOUT); + + while (!timedOut) { + await new Promise(resolve => setTimeout(resolve, 2000)); // poll every two seconds + const response = await fetch( + `https://sentry.io/api/0/projects/${sentryTestOrgSlug}/${sentryTestProject}/events/${eventId}/json/`, + { headers: { Authorization: `Bearer ${authToken}` } }, + ); + + // Only allow ok responses or 404 + if (!response.ok) { + expect(response.status).toBe(404); + continue; + } + + const eventPayload = await response.json(); + const frames = eventPayload.exception?.values?.[0]?.stacktrace?.frames; + const topFrame = frames[frames.length - 1]; + expect({ + preContext: topFrame?.pre_context, + contextLine: topFrame?.context_line, + postContext: topFrame?.post_context, + lineno: topFrame?.lineno, + colno: topFrame?.colno, + }).toMatchSnapshot(); + return; + } + + throw new Error('Test timed out'); + }, + { timeout: EVENT_POLLING_TIMEOUT }, +); diff --git a/packages/feedback/.eslintignore b/packages/feedback/.eslintignore new file mode 100644 index 000000000000..b38db2f296ff --- /dev/null +++ b/packages/feedback/.eslintignore @@ -0,0 +1,2 @@ +node_modules/ +build/ diff --git a/packages/feedback/.eslintrc.js b/packages/feedback/.eslintrc.js new file mode 100644 index 000000000000..cf9985e769c0 --- /dev/null +++ b/packages/feedback/.eslintrc.js @@ -0,0 +1,36 @@ +// Note: All paths are relative to the directory in which eslint is being run, rather than the directory where this file +// lives + +// ESLint config docs: https://eslint.org/docs/user-guide/configuring/ + +module.exports = { + extends: ['../../.eslintrc.js'], + overrides: [ + { + files: ['jest.setup.ts', 'jest.config.ts'], + parserOptions: { + project: ['tsconfig.test.json'], + }, + rules: { + 'no-console': 'off', + }, + }, + { + files: ['test/**/*.ts'], + + rules: { + // most of these errors come from `new Promise(process.nextTick)` + '@typescript-eslint/unbound-method': 'off', + // TODO: decide if we want to enable this again after the migration + // We can take the freedom to be a bit more lenient with tests + '@typescript-eslint/no-floating-promises': 'off', + }, + }, + { + files: ['src/types/deprecated.ts'], + rules: { + '@typescript-eslint/naming-convention': 'off', + }, + }, + ], +}; diff --git a/packages/feedback/.gitignore b/packages/feedback/.gitignore new file mode 100644 index 000000000000..363d3467c6fa --- /dev/null +++ b/packages/feedback/.gitignore @@ -0,0 +1,4 @@ +node_modules +/*.tgz +.eslintcache +build diff --git a/packages/feedback/LICENSE b/packages/feedback/LICENSE new file mode 100644 index 000000000000..d11896ba1181 --- /dev/null +++ b/packages/feedback/LICENSE @@ -0,0 +1,14 @@ +Copyright (c) 2023 Sentry (https://sentry.io) and individual contributors. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/feedback/README.md b/packages/feedback/README.md new file mode 100644 index 000000000000..799cea6b59b8 --- /dev/null +++ b/packages/feedback/README.md @@ -0,0 +1,99 @@ +

+ + Sentry + +

+ +# Sentry Integration for Feedback + +This SDK is **considered experimental and in an alpha state**. It may experience breaking changes, and may be discontinued at any time. Please reach out on +[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback/concerns. + +## Pre-requisites + +`@sentry/feedback` currently can only be used by browsers with [Shadow DOM](https://developer.mozilla.org/en-US/docs/Web/API/Web_components/Using_shadow_DOM) support. + +## Installation + +Feedback can be imported from `@sentry/browser`, or a respective SDK package like `@sentry/react` or `@sentry/vue`. +You don't need to install anything in order to use Feedback. The minimum version that includes Feedback is <>. + +For details on using Feedback when using Sentry via the CDN bundles, see [CDN bundle](#loading-feedback-as-a-cdn-bundle). + +## Setup + +To set up the integration, add the following to your Sentry initialization. Several options are supported and passable via the integration constructor. +See the [configuration section](#configuration) below for more details. + +```javascript +import * as Sentry from '@sentry/browser'; +// or e.g. import * as Sentry from '@sentry/react'; + +Sentry.init({ + dsn: '__DSN__', + integrations: [ + new Sentry.Feedback({ + // Additional SDK configuration goes in here, for example: + // See below for all available options + }) + ], + // ... +}); +``` + +### Lazy loading Feedback + +Feedback will start automatically when you add the integration. +If you do not want to start Feedback immediately (e.g. if you want to lazy-load it), +you can also use `addIntegration` to load it later: + +```js +import * as Sentry from "@sentry/react"; +import { BrowserClient } from "@sentry/browser"; + +Sentry.init({ + // Do not load it initially + integrations: [] +}); + +// Sometime later +const { Feedback } = await import('@sentry/browser'); +const client = Sentry.getCurrentHub().getClient(); + +// Client can be undefined +client?.addIntegration(new Feedback()); +``` + +### Identifying Users + +If you have only followed the above instructions to setup session feedbacks, you will only see IP addresses in Sentry's UI. In order to associate a user identity to a session feedback, use [`setUser`](https://docs.sentry.io/platforms/javascript/enriching-events/identify-user/). + +```javascript +import * as Sentry from "@sentry/browser"; + +Sentry.setUser({ email: "jane.doe@example.com" }); +``` + +## Loading Feedback as a CDN Bundle + +As an alternative to the NPM package, you can use Feedback as a CDN bundle. +Please refer to the [Feedback installation guide](https://docs.sentry.io/platforms/javascript/session-feedback/#install) for CDN bundle instructions. + + +## Configuration + +### General Integration Configuration + +The following options can be configured as options to the integration, in `new Feedback({})`: + +| key | type | default | description | +| --------- | ------- | ------- | ----------- | +| tbd | boolean | `true` | tbd | + + + +## Manually Sending Feedback Data + +Connect your own feedback UI to Sentry's You can use `feedback.flush()` to immediately send all currently captured feedback data. +When Feedback is currently in buffering mode, this will send up to the last 60 seconds of feedback data, +and also continue sending afterwards, similar to when an error happens & is recorded. diff --git a/packages/feedback/jest.config.js b/packages/feedback/jest.config.js new file mode 100644 index 000000000000..24f49ab59a4c --- /dev/null +++ b/packages/feedback/jest.config.js @@ -0,0 +1 @@ +module.exports = require('../../jest/jest.config.js'); diff --git a/packages/feedback/package.json b/packages/feedback/package.json new file mode 100644 index 000000000000..23e2d3c81f09 --- /dev/null +++ b/packages/feedback/package.json @@ -0,0 +1,61 @@ +{ + "name": "@sentry-internal/feedback", + "version": "7.70.0", + "description": "Sentry SDK integration for user feedback", + "repository": "git://github.com/getsentry/sentry-javascript.git", + "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/feedback", + "author": "Sentry", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "main": "build/npm/cjs/index.js", + "module": "build/npm/esm/index.js", + "types": "build/npm/types/index.d.ts", + "typesVersions": { + "<4.9": { + "build/npm/types/index.d.ts": [ + "build/npm/types-ts3.8/index.d.ts" + ] + } + }, + "publishConfig": { + "access": "public" + }, + "dependencies": { + "@sentry/core": "7.70.0", + "@sentry/types": "7.70.0", + "@sentry/utils": "7.70.0", + "tslib": "^2.4.1 || ^1.9.3" + }, + "scripts": { + "build": "run-p build:transpile build:types build:bundle", + "build:transpile": "rollup -c rollup.npm.config.js", + "build:bundle": "rollup -c rollup.bundle.config.js", + "build:dev": "run-p build:transpile build:types", + "build:types": "run-s build:types:core build:types:downlevel", + "build:types:core": "tsc -p tsconfig.types.json", + "build:types:downlevel": "yarn downlevel-dts build/npm/types build/npm/types-ts3.8 --to ts3.8", + "build:watch": "run-p build:transpile:watch build:bundle:watch build:types:watch", + "build:dev:watch": "run-p build:transpile:watch build:types:watch", + "build:transpile:watch": "yarn build:transpile --watch", + "build:bundle:watch": "yarn build:bundle --watch", + "build:types:watch": "tsc -p tsconfig.types.json --watch", + "build:tarball": "ts-node ../../scripts/prepack.ts --bundles && npm pack ./build/npm", + "circularDepCheck": "madge --circular src/index.ts", + "clean": "rimraf build sentry-replay-*.tgz", + "fix": "run-s fix:eslint fix:prettier", + "fix:eslint": "eslint . --format stylish --fix", + "fix:prettier": "prettier --write \"{src,test,scripts}/**/*.ts\"", + "lint": "run-s lint:prettier lint:eslint", + "lint:eslint": "eslint . --format stylish", + "lint:prettier": "prettier --check \"{src,test,scripts}/**/*.ts\"", + "test": "jest", + "test:watch": "jest --watch", + "yalc:publish": "ts-node ../../scripts/prepack.ts --bundles && yalc publish ./build/npm --push" + }, + "volta": { + "extends": "../../package.json" + }, + "sideEffects": false +} diff --git a/packages/feedback/rollup.bundle.config.js b/packages/feedback/rollup.bundle.config.js new file mode 100644 index 000000000000..185b38249dc0 --- /dev/null +++ b/packages/feedback/rollup.bundle.config.js @@ -0,0 +1,13 @@ +import { makeBaseBundleConfig, makeBundleConfigVariants } from '../../rollup/index.js'; + +const baseBundleConfig = makeBaseBundleConfig({ + bundleType: 'addon', + entrypoints: ['src/index.ts'], + jsVersion: 'es6', + licenseTitle: '@sentry-internal/feedback', + outputFileBase: () => 'bundles/feedback', +}); + +const builds = makeBundleConfigVariants(baseBundleConfig); + +export default builds; diff --git a/packages/feedback/rollup.npm.config.js b/packages/feedback/rollup.npm.config.js new file mode 100644 index 000000000000..e823e7b18863 --- /dev/null +++ b/packages/feedback/rollup.npm.config.js @@ -0,0 +1,16 @@ +import { makeBaseNPMConfig, makeNPMConfigVariants } from '../../rollup/index'; + +export default makeNPMConfigVariants( + makeBaseNPMConfig({ + hasBundles: true, + packageSpecificConfig: { + output: { + // set exports to 'named' or 'auto' so that rollup doesn't warn + exports: 'named', + // set preserveModules to false because for feedback we actually want + // to bundle everything into one file. + preserveModules: false, + }, + }, + }), +); diff --git a/packages/feedback/src/index.ts b/packages/feedback/src/index.ts new file mode 100644 index 000000000000..834e9dcce670 --- /dev/null +++ b/packages/feedback/src/index.ts @@ -0,0 +1 @@ +export { sendFeedbackRequest } from './util/sendFeedbackRequest'; diff --git a/packages/feedback/src/types/index.ts b/packages/feedback/src/types/index.ts new file mode 100644 index 000000000000..01a12814c88b --- /dev/null +++ b/packages/feedback/src/types/index.ts @@ -0,0 +1,29 @@ +import type { Event, Primitive } from '@sentry/types'; + +export type SentryTags = { [key: string]: Primitive } | undefined; + +/** + * NOTE: These types are still considered Beta and subject to change. + * @hidden + */ +export interface FeedbackEvent extends Event { + feedback: { + message: string; + url: string; + contact_email?: string; + name?: string; + replay_id?: string; + }; + // TODO: Add this event type to Event + // type: 'feedback_event'; +} + +export interface SendFeedbackData { + feedback: { + message: string; + url: string; + email?: string; + replay_id?: string; + name?: string; + }; +} diff --git a/packages/feedback/src/util/prepareFeedbackEvent.ts b/packages/feedback/src/util/prepareFeedbackEvent.ts new file mode 100644 index 000000000000..6d32506d3be4 --- /dev/null +++ b/packages/feedback/src/util/prepareFeedbackEvent.ts @@ -0,0 +1,52 @@ +import type { Scope } from '@sentry/core'; +import { prepareEvent } from '@sentry/core'; +import type { Client } from '@sentry/types'; + +import type { FeedbackEvent } from '../types'; + +interface PrepareFeedbackEventParams { + client: Client; + event: FeedbackEvent; + scope: Scope; +} +/** + * Prepare a feedback event & enrich it with the SDK metadata. + */ +export async function prepareFeedbackEvent({ + client, + scope, + event, +}: PrepareFeedbackEventParams): Promise { + const eventHint = { integrations: undefined }; + if (client.emit) { + client.emit('preprocessEvent', event, eventHint); + } + + const preparedEvent = (await prepareEvent( + client.getOptions(), + event, + { integrations: undefined }, + scope, + )) as FeedbackEvent | null; + + // If e.g. a global event processor returned null + if (!preparedEvent) { + return null; + } + + // This normally happens in browser client "_prepareEvent" + // but since we do not use this private method from the client, but rather the plain import + // we need to do this manually. + preparedEvent.platform = preparedEvent.platform || 'javascript'; + + // extract the SDK name because `client._prepareEvent` doesn't add it to the event + const metadata = client.getSdkMetadata && client.getSdkMetadata(); + const { name, version } = (metadata && metadata.sdk) || {}; + + preparedEvent.sdk = { + ...preparedEvent.sdk, + name: name || 'sentry.javascript.unknown', + version: version || '0.0.0', + }; + return preparedEvent; +} diff --git a/packages/feedback/src/util/sendFeedbackRequest.ts b/packages/feedback/src/util/sendFeedbackRequest.ts new file mode 100644 index 000000000000..626457d6122b --- /dev/null +++ b/packages/feedback/src/util/sendFeedbackRequest.ts @@ -0,0 +1,113 @@ +import { getCurrentHub } from '@sentry/core'; +import { dsnToString } from '@sentry/utils'; + +import type { SendFeedbackData } from '../types'; +import { prepareFeedbackEvent } from './prepareFeedbackEvent'; + +/** + * Send feedback using `fetch()` + */ +export async function sendFeedbackRequest({ + feedback: { message, email, name, replay_id, url }, +}: SendFeedbackData): Promise { + const hub = getCurrentHub(); + + if (!hub) { + return null; + } + + const client = hub.getClient(); + const scope = hub.getScope(); + const transport = client && client.getTransport(); + const dsn = client && client.getDsn(); + + if (!client || !transport || !dsn) { + return null; + } + + const baseEvent = { + feedback: { + contact_email: email, + name, + message, + replay_id, + url, + }, + // type: 'feedback_event', + }; + + const feedbackEvent = await prepareFeedbackEvent({ + scope, + client, + event: baseEvent, + }); + + if (!feedbackEvent) { + // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions + // client.recordDroppedEvent('event_processor', 'feedback', baseEvent); + return null; + } + + /* + For reference, the fully built event looks something like this: + { + "data": { + "dist": "abc123", + "environment": "production", + "feedback": { + "contact_email": "colton.allen@sentry.io", + "message": "I really like this user-feedback feature!", + "replay_id": "ec3b4dc8b79f417596f7a1aa4fcca5d2", + "url": "https://docs.sentry.io/platforms/javascript/" + }, + "id": "1ffe0775ac0f4417aed9de36d9f6f8dc", + "platform": "javascript", + "release": "version@1.3", + "request": { + "headers": { + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36" + } + }, + "sdk": { + "name": "sentry.javascript.react", + "version": "6.18.1" + }, + "tags": { + "key": "value" + }, + "timestamp": "2023-08-31T14:10:34.954048", + "user": { + "email": "username@example.com", + "id": "123", + "ip_address": "127.0.0.1", + "name": "user", + "username": "user2270129" + } + } + } + */ + + // Prevent this data (which, if it exists, was used in earlier steps in the processing pipeline) from being sent to + // sentry. (Note: Our use of this property comes and goes with whatever we might be debugging, whatever hacks we may + // have temporarily added, etc. Even if we don't happen to be using it at some point in the future, let's not get rid + // of this `delete`, lest we miss putting it back in the next time the property is in use.) + delete feedbackEvent.sdkProcessingMetadata; + + try { + const path = 'https://sentry.io/api/0/feedback/'; + const response = await fetch(path, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `DSN ${dsnToString(dsn)}`, + }, + body: JSON.stringify(feedbackEvent), + }); + if (!response.ok) { + return null; + } + return response; + } catch (err) { + return null; + } +} diff --git a/packages/feedback/test/index.ts b/packages/feedback/test/index.ts new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts b/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts new file mode 100644 index 000000000000..cee0d99f0b5c --- /dev/null +++ b/packages/feedback/test/unit/util/prepareFeedbackEvent.test.ts @@ -0,0 +1,87 @@ +import type { Hub, Scope } from '@sentry/core'; +import { getCurrentHub } from '@sentry/core'; +import type { Client } from '@sentry/types'; + +import type { FeedbackEvent } from '../../../src/types'; +import { prepareFeedbackEvent } from '../../../src/util/prepareFeedbackEvent'; +import { getDefaultClientOptions, TestClient } from '../../utils/TestClient'; + +describe('Unit | util | prepareFeedbackEvent', () => { + let hub: Hub; + let client: Client; + let scope: Scope; + + beforeEach(() => { + hub = getCurrentHub(); + client = new TestClient(getDefaultClientOptions()); + hub.bindClient(client); + + client = hub.getClient()!; + scope = hub.getScope()!; + + jest.spyOn(client, 'getSdkMetadata').mockImplementation(() => { + return { + sdk: { + name: 'sentry.javascript.testSdk', + version: '1.0.0', + }, + }; + }); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('works', async () => { + expect(client).toBeDefined(); + expect(scope).toBeDefined(); + + const replayId = 'replay-ID'; + const event: FeedbackEvent = { + timestamp: 1670837008.634, + event_id: 'feedback-ID', + feedback: { + contact_email: 'test@test.com', + message: 'looks great!', + replay_id: replayId, + url: 'https://sentry.io/', + }, + contexts: { + replay: { + error_sample_rate: 1.0, + session_sample_rate: 0.1, + }, + }, + }; + + const feedbackEvent = await prepareFeedbackEvent({ scope, client, event }); + + expect(client.getSdkMetadata).toHaveBeenCalledTimes(1); + + expect(feedbackEvent).toEqual({ + timestamp: 1670837008.634, + event_id: 'feedback-ID', + feedback: { + contact_email: 'test@test.com', + message: 'looks great!', + replay_id: replayId, + url: 'https://sentry.io/', + }, + platform: 'javascript', + environment: 'production', + contexts: { + replay: { + error_sample_rate: 1.0, + session_sample_rate: 0.1, + }, + }, + sdk: { + name: 'sentry.javascript.testSdk', + version: '1.0.0', + }, + sdkProcessingMetadata: expect.any(Object), + breadcrumbs: undefined, + }); + }); +}); diff --git a/packages/feedback/test/utils/TestClient.ts b/packages/feedback/test/utils/TestClient.ts new file mode 100644 index 000000000000..ad39b82084a9 --- /dev/null +++ b/packages/feedback/test/utils/TestClient.ts @@ -0,0 +1,44 @@ +import { BaseClient, createTransport, initAndBind } from '@sentry/core'; +import type { BrowserClientReplayOptions, ClientOptions, Event, SeverityLevel } from '@sentry/types'; +import { resolvedSyncPromise } from '@sentry/utils'; + +export interface TestClientOptions extends ClientOptions, BrowserClientReplayOptions {} + +export class TestClient extends BaseClient { + public constructor(options: TestClientOptions) { + super(options); + } + + public eventFromException(exception: any): PromiseLike { + return resolvedSyncPromise({ + exception: { + values: [ + { + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + type: exception.name, + value: exception.message, + /* eslint-enable @typescript-eslint/no-unsafe-member-access */ + }, + ], + }, + }); + } + + public eventFromMessage(message: string, level: SeverityLevel = 'info'): PromiseLike { + return resolvedSyncPromise({ message, level }); + } +} + +export function init(options: TestClientOptions): void { + initAndBind(TestClient, options); +} + +export function getDefaultClientOptions(options: Partial = {}): ClientOptions { + return { + integrations: [], + dsn: 'https://username@domain/123', + transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), + stackParser: () => [], + ...options, + }; +} diff --git a/packages/feedback/tsconfig.json b/packages/feedback/tsconfig.json new file mode 100644 index 000000000000..f8f54556da93 --- /dev/null +++ b/packages/feedback/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "esnext" + }, + "include": ["src/**/*.ts"] +} diff --git a/packages/feedback/tsconfig.test.json b/packages/feedback/tsconfig.test.json new file mode 100644 index 000000000000..ad87caa06c48 --- /dev/null +++ b/packages/feedback/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.json", + + "include": ["test/**/*.ts", "jest.config.ts", "jest.setup.ts"], + + "compilerOptions": { + "types": ["node", "jest"], + "esModuleInterop": true, + "allowJs": true, + "noImplicitAny": true, + "noImplicitThis": false, + "strictNullChecks": true, + "strictPropertyInitialization": false + } +} diff --git a/packages/feedback/tsconfig.types.json b/packages/feedback/tsconfig.types.json new file mode 100644 index 000000000000..374fd9bc9364 --- /dev/null +++ b/packages/feedback/tsconfig.types.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + + "compilerOptions": { + "declaration": true, + "declarationMap": true, + "emitDeclarationOnly": true, + "outDir": "build/npm/types" + } +} diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json index 13237cb5d69d..0aef2f1e380a 100644 --- a/packages/nextjs/package.json +++ b/packages/nextjs/package.json @@ -34,11 +34,13 @@ "@sentry/vercel-edge": "7.74.0", "@sentry/webpack-plugin": "1.20.0", "chalk": "3.0.0", + "resolve": "1.22.8", "rollup": "2.78.0", "stacktrace-parser": "^0.1.10", "tslib": "^2.4.1 || ^1.9.3" }, "devDependencies": { + "@types/resolve": "1.20.3", "@types/webpack": "^4.41.31", "eslint-plugin-react": "^7.31.11", "next": "10.1.3" diff --git a/packages/nextjs/src/config/webpack.ts b/packages/nextjs/src/config/webpack.ts index ffe7091fa74a..2594623fb5bb 100644 --- a/packages/nextjs/src/config/webpack.ts +++ b/packages/nextjs/src/config/webpack.ts @@ -6,6 +6,7 @@ import type SentryCliPlugin from '@sentry/webpack-plugin'; import * as chalk from 'chalk'; import * as fs from 'fs'; import * as path from 'path'; +import { sync as resolveSync } from 'resolve'; import type { VercelCronsConfig } from '../common/types'; // Note: If you need to import a type from Webpack, do it in `types.ts` and export it from there. Otherwise, our @@ -126,7 +127,10 @@ export function constructWebpackConfigFunction( pageExtensionRegex, excludeServerRoutes: userSentryOptions.excludeServerRoutes, sentryConfigFilePath: getUserConfigFilePath(projectDir, runtime), - nextjsRequestAsyncStorageModulePath: getRequestAsyncStorageModuleLocation(rawNewConfig.resolve?.modules), + nextjsRequestAsyncStorageModulePath: getRequestAsyncStorageModuleLocation( + projectDir, + rawNewConfig.resolve?.modules, + ), }; const normalizeLoaderResourcePath = (resourcePath: string): string => { @@ -478,7 +482,7 @@ async function addSentryToEntryProperty( // we know is that it won't have gotten *simpler* in form, so we only need to worry about the object and function // options. See https://webpack.js.org/configuration/entry-context/#entry. - const { isServer, dir: projectDir, nextRuntime } = buildContext; + const { isServer, dir: projectDir, nextRuntime, dev: isDevMode } = buildContext; const runtime = isServer ? (buildContext.nextRuntime === 'edge' ? 'edge' : 'node') : 'browser'; const newEntryProperty = @@ -498,7 +502,7 @@ async function addSentryToEntryProperty( // inject into all entry points which might contain user's code for (const entryPointName in newEntryProperty) { if (shouldAddSentryToEntryPoint(entryPointName, runtime)) { - addFilesToExistingEntryPoint(newEntryProperty, entryPointName, filesToInject); + addFilesToExistingEntryPoint(newEntryProperty, entryPointName, filesToInject, isDevMode); } else { if ( isServer && @@ -566,31 +570,44 @@ export function getUserConfigFilePath(projectDir: string, platform: 'server' | ' * * @param entryProperty The existing `entry` config object * @param entryPointName The key where the file should be injected - * @param filepaths An array of paths to the injected files + * @param filesToInsert An array of paths to the injected files */ function addFilesToExistingEntryPoint( entryProperty: EntryPropertyObject, entryPointName: string, - filepaths: string[], + filesToInsert: string[], + isDevMode: boolean, ): void { + // BIG FAT NOTE: Order of insertion seems to matter here. If we insert the new files before the `currentEntrypoint`s, + // the Next.js dev server breaks. Because we generally still want the SDK to be initialized as early as possible we + // still keep it at the start of the entrypoints if we are not in dev mode. + // can be a string, array of strings, or object whose `import` property is one of those two const currentEntryPoint = entryProperty[entryPointName]; let newEntryPoint = currentEntryPoint; - if (typeof currentEntryPoint === 'string') { - newEntryPoint = [...filepaths, currentEntryPoint]; - } else if (Array.isArray(currentEntryPoint)) { - newEntryPoint = [...filepaths, ...currentEntryPoint]; + if (typeof currentEntryPoint === 'string' || Array.isArray(currentEntryPoint)) { + newEntryPoint = arrayify(currentEntryPoint); + + if (isDevMode) { + // Inserting at beginning breaks dev mode so we insert at the end + newEntryPoint.push(...filesToInsert); + } else { + // In other modes we insert at the beginning so that the SDK initializes as early as possible + newEntryPoint.unshift(...filesToInsert); + } } // descriptor object (webpack 5+) else if (typeof currentEntryPoint === 'object' && 'import' in currentEntryPoint) { const currentImportValue = currentEntryPoint.import; - let newImportValue; + const newImportValue = arrayify(currentImportValue); - if (typeof currentImportValue === 'string') { - newImportValue = [...filepaths, currentImportValue]; + if (isDevMode) { + // Inserting at beginning breaks dev mode so we insert at the end + newImportValue.push(...filesToInsert); } else { - newImportValue = [...filepaths, ...currentImportValue]; + // In other modes we insert at the beginning so that the SDK initializes as early as possible + newImportValue.unshift(...filesToInsert); } newEntryPoint = { @@ -977,39 +994,47 @@ function addValueInjectionLoader( ); } +function resolveNextPackageDirFromDirectory(basedir: string): string | undefined { + try { + return path.dirname(resolveSync('next/package.json', { basedir })); + } catch { + // Should not happen in theory + return undefined; + } +} + +const POTENTIAL_REQUEST_ASNYC_STORAGE_LOCATIONS = [ + // Original location of RequestAsyncStorage + // https://github.com/vercel/next.js/blob/46151dd68b417e7850146d00354f89930d10b43b/packages/next/src/client/components/request-async-storage.ts + 'next/dist/client/components/request-async-storage.js', + // Introduced in Next.js 13.4.20 + // https://github.com/vercel/next.js/blob/e1bc270830f2fc2df3542d4ef4c61b916c802df3/packages/next/src/client/components/request-async-storage.external.ts + 'next/dist/client/components/request-async-storage.external.js', +]; + function getRequestAsyncStorageModuleLocation( + webpackContextDir: string, webpackResolvableModuleLocations: string[] | undefined, ): string | undefined { if (webpackResolvableModuleLocations === undefined) { return undefined; } - const absoluteWebpackResolvableModuleLocations = webpackResolvableModuleLocations.map(m => path.resolve(m)); - const moduleIsWebpackResolvable = (moduleId: string): boolean => { - let requireResolveLocation: string; - try { - // This will throw if the location is not resolvable at all. - // We provide a `paths` filter in order to maximally limit the potential locations to the locations webpack would check. - requireResolveLocation = require.resolve(moduleId, { paths: webpackResolvableModuleLocations }); - } catch { - return false; - } - - // Since the require.resolve approach still looks in "global" node_modules locations like for example "/user/lib/node" - // we further need to filter by locations that start with the locations that webpack would check for. - return absoluteWebpackResolvableModuleLocations.some(resolvableModuleLocation => - requireResolveLocation.startsWith(resolvableModuleLocation), - ); - }; + const absoluteWebpackResolvableModuleLocations = webpackResolvableModuleLocations.map(loc => + path.resolve(webpackContextDir, loc), + ); - const potentialRequestAsyncStorageLocations = [ - // Original location of RequestAsyncStorage - // https://github.com/vercel/next.js/blob/46151dd68b417e7850146d00354f89930d10b43b/packages/next/src/client/components/request-async-storage.ts - 'next/dist/client/components/request-async-storage', - // Introduced in Next.js 13.4.20 - // https://github.com/vercel/next.js/blob/e1bc270830f2fc2df3542d4ef4c61b916c802df3/packages/next/src/client/components/request-async-storage.external.ts - 'next/dist/client/components/request-async-storage.external', - ]; + for (const webpackResolvableLocation of absoluteWebpackResolvableModuleLocations) { + const nextPackageDir = resolveNextPackageDirFromDirectory(webpackResolvableLocation); + if (nextPackageDir) { + const asyncLocalStorageLocation = POTENTIAL_REQUEST_ASNYC_STORAGE_LOCATIONS.find(loc => + fs.existsSync(path.join(nextPackageDir, '..', loc)), + ); + if (asyncLocalStorageLocation) { + return asyncLocalStorageLocation; + } + } + } - return potentialRequestAsyncStorageLocations.find(potentialLocation => moduleIsWebpackResolvable(potentialLocation)); + return undefined; } diff --git a/packages/node-experimental/src/integrations/node-fetch.ts b/packages/node-experimental/src/integrations/node-fetch.ts index 73c1eaab27ae..9afd70be62e7 100644 --- a/packages/node-experimental/src/integrations/node-fetch.ts +++ b/packages/node-experimental/src/integrations/node-fetch.ts @@ -1,8 +1,8 @@ import type { Span } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; -import { registerInstrumentations } from '@opentelemetry/instrumentation'; +import type { Instrumentation } from '@opentelemetry/instrumentation'; import { hasTracingEnabled } from '@sentry/core'; -import type { EventProcessor, Hub, Integration } from '@sentry/types'; +import type { Integration } from '@sentry/types'; import { FetchInstrumentation } from 'opentelemetry-instrumentation-fetch-node'; import { OTEL_ATTR_ORIGIN } from '../constants'; @@ -10,6 +10,7 @@ import type { NodeExperimentalClient } from '../sdk/client'; import { getCurrentHub } from '../sdk/hub'; import { getRequestSpanData } from '../utils/getRequestSpanData'; import { getSpanKind } from '../utils/getSpanKind'; +import { NodePerformanceIntegration } from './NodePerformanceIntegration'; interface NodeFetchOptions { /** @@ -31,7 +32,7 @@ interface NodeFetchOptions { * * Create breadcrumbs for outgoing requests * * Create spans for outgoing requests */ -export class NodeFetch implements Integration { +export class NodeFetch extends NodePerformanceIntegration implements Integration { /** * @inheritDoc */ @@ -47,7 +48,6 @@ export class NodeFetch implements Integration { */ public shouldCreateSpansForRequests: boolean; - private _unload?: () => void; private readonly _breadcrumbs: boolean; // If this is undefined, use default behavior based on client settings private readonly _spans: boolean | undefined; @@ -56,6 +56,8 @@ export class NodeFetch implements Integration { * @inheritDoc */ public constructor(options: NodeFetchOptions = {}) { + super(options); + this.name = NodeFetch.id; this._breadcrumbs = typeof options.breadcrumbs === 'undefined' ? true : options.breadcrumbs; this._spans = typeof options.spans === 'undefined' ? undefined : options.spans; @@ -64,14 +66,23 @@ export class NodeFetch implements Integration { this.shouldCreateSpansForRequests = false; } + /** @inheritDoc */ + public setupInstrumentation(): void | Instrumentation[] { + return [ + new FetchInstrumentation({ + onRequest: ({ span }: { span: Span }) => { + this._updateSpan(span); + this._addRequestBreadcrumb(span); + }, + }), + ]; + } + /** * @inheritDoc */ - public setupOnce(_addGlobalEventProcessor: (callback: EventProcessor) => void, _getCurrentHub: () => Hub): void { - // No need to instrument if we don't want to track anything - if (!this._breadcrumbs && this._spans === false) { - return; - } + public setupOnce(): void { + super.setupOnce(); const client = getCurrentHub().getClient(); const clientOptions = client?.getOptions(); @@ -79,18 +90,6 @@ export class NodeFetch implements Integration { // This is used in the sampler function this.shouldCreateSpansForRequests = typeof this._spans === 'boolean' ? this._spans : hasTracingEnabled(clientOptions); - - // Register instrumentations we care about - this._unload = registerInstrumentations({ - instrumentations: [ - new FetchInstrumentation({ - onRequest: ({ span }: { span: Span }) => { - this._updateSpan(span); - this._addRequestBreadcrumb(span); - }, - }), - ], - }); } /** diff --git a/packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts b/packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts new file mode 100644 index 000000000000..6083d1a85f91 --- /dev/null +++ b/packages/node-integration-tests/suites/express/multiple-routers/complex-router/server.ts @@ -0,0 +1,35 @@ +import * as Sentry from '@sentry/node'; +import * as Tracing from '@sentry/tracing'; +import express from 'express'; + +const app = express(); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + // eslint-disable-next-line deprecation/deprecation + integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + tracesSampleRate: 1.0, +}); + +app.use(Sentry.Handlers.requestHandler()); +app.use(Sentry.Handlers.tracingHandler()); + +const APIv1 = express.Router(); + +APIv1.use( + '/users/:userId', + APIv1.get('/posts/:postId', (_req, res) => { + Sentry.captureMessage('Custom Message'); + return res.send('Success'); + }), +); + +const router = express.Router(); + +app.use('/api', router); +app.use('/api/api/v1', APIv1.use('/sub-router', APIv1)); + +app.use(Sentry.Handlers.errorHandler()); + +export default app; diff --git a/packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts b/packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts new file mode 100644 index 000000000000..b8079bfdc0ac --- /dev/null +++ b/packages/node-integration-tests/suites/express/multiple-routers/complex-router/test.ts @@ -0,0 +1,27 @@ +import { assertSentryEvent, TestEnv } from '../../../../utils/index'; + +test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route and express used multiple middlewares with route', async () => { + const env = await TestEnv.init(__dirname, `${__dirname}/server.ts`); + const event = await env.getEnvelopeRequest({ + url: env.url.replace('test', 'api/api/v1/sub-router/users/123/posts/456'), + envelopeType: 'transaction', + }); + // parse node.js major version + const [major] = process.versions.node.split('.').map(Number); + // Split test result base on major node version because regex d flag is support from node 16+ + if (major >= 16) { + assertSentryEvent(event[2] as any, { + transaction: 'GET /api/api/v1/sub-router/users/:userId/posts/:postId', + transaction_info: { + source: 'route', + }, + }); + } else { + assertSentryEvent(event[2] as any, { + transaction: 'GET /api/api/v1/sub-router/users/123/posts/:postId', + transaction_info: { + source: 'route', + }, + }); + } +}); diff --git a/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts b/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts new file mode 100644 index 000000000000..19cb3c544bde --- /dev/null +++ b/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/server.ts @@ -0,0 +1,35 @@ +import * as Sentry from '@sentry/node'; +import * as Tracing from '@sentry/tracing'; +import express from 'express'; + +const app = express(); + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', + release: '1.0', + // eslint-disable-next-line deprecation/deprecation + integrations: [new Sentry.Integrations.Http({ tracing: true }), new Tracing.Integrations.Express({ app })], + tracesSampleRate: 1.0, +}); + +app.use(Sentry.Handlers.requestHandler()); +app.use(Sentry.Handlers.tracingHandler()); + +const APIv1 = express.Router(); + +APIv1.use( + '/users/:userId', + APIv1.get('/posts/:postId', (_req, res) => { + Sentry.captureMessage('Custom Message'); + return res.send('Success'); + }), +); + +const root = express.Router(); + +app.use('/api/v1', APIv1); +app.use('/api', root); + +app.use(Sentry.Handlers.errorHandler()); + +export default app; diff --git a/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts b/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts new file mode 100644 index 000000000000..3527cce5c3a6 --- /dev/null +++ b/packages/node-integration-tests/suites/express/multiple-routers/middle-layer-parameterized/test.ts @@ -0,0 +1,28 @@ +import { assertSentryEvent, TestEnv } from '../../../../utils/index'; + +test('should construct correct url with multiple parameterized routers, when param is also contain in middle layer route', async () => { + const env = await TestEnv.init(__dirname, `${__dirname}/server.ts`); + const event = await env.getEnvelopeRequest({ + url: env.url.replace('test', 'api/v1/users/123/posts/456'), + envelopeType: 'transaction', + }); + + // parse node.js major version + const [major] = process.versions.node.split('.').map(Number); + // Split test result base on major node version because regex d flag is support from node 16+ + if (major >= 16) { + assertSentryEvent(event[2] as any, { + transaction: 'GET /api/v1/users/:userId/posts/:postId', + transaction_info: { + source: 'route', + }, + }); + } else { + assertSentryEvent(event[2] as any, { + transaction: 'GET /api/v1/users/123/posts/:postId', + transaction_info: { + source: 'route', + }, + }); + } +}); diff --git a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js index 823fa966aa2a..e04783b9460a 100644 --- a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js +++ b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.js @@ -4,7 +4,6 @@ const Sentry = require('@sentry/node'); Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', includeLocalVariables: true, - integrations: [new Sentry.Integrations.LocalVariables({ captureAllExceptions: true })], beforeSend: event => { // eslint-disable-next-line no-console console.log(JSON.stringify(event)); diff --git a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs index dfb46f29326e..ea2c1eb2ff68 100644 --- a/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs +++ b/packages/node-integration-tests/suites/public-api/LocalVariables/local-variables-caught.mjs @@ -4,7 +4,6 @@ import * as Sentry from '@sentry/node'; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', includeLocalVariables: true, - integrations: [new Sentry.Integrations.LocalVariables({ captureAllExceptions: true })], beforeSend: event => { // eslint-disable-next-line no-console console.log(JSON.stringify(event)); diff --git a/packages/node/src/anr/index.ts b/packages/node/src/anr/index.ts index 1d4d61b78b55..9c6827c38259 100644 --- a/packages/node/src/anr/index.ts +++ b/packages/node/src/anr/index.ts @@ -138,6 +138,20 @@ function startChildProcess(options: Options): void { } } +function createHrTimer(): { getTimeMs: () => number; reset: () => void } { + let lastPoll = process.hrtime(); + + return { + getTimeMs: (): number => { + const [seconds, nanoSeconds] = process.hrtime(lastPoll); + return Math.floor(seconds * 1e3 + nanoSeconds / 1e6); + }, + reset: (): void => { + lastPoll = process.hrtime(); + }, + }; +} + function handleChildProcess(options: Options): void { function log(message: string): void { logger.log(`[ANR child process] ${message}`); @@ -182,7 +196,7 @@ function handleChildProcess(options: Options): void { } } - const { poll } = watchdogTimer(options.pollInterval, options.anrThreshold, watchdogTimeout); + const { poll } = watchdogTimer(createHrTimer, options.pollInterval, options.anrThreshold, watchdogTimeout); process.on('message', () => { poll(); diff --git a/packages/node/src/requestdata.ts b/packages/node/src/requestdata.ts index bc07fcf92f8b..e746db088a95 100644 --- a/packages/node/src/requestdata.ts +++ b/packages/node/src/requestdata.ts @@ -109,7 +109,9 @@ function extractTransaction(req: PolymorphicRequest, type: boolean | Transaction } case 'methodPath': default: { - return extractPathForTransaction(req, { path: true, method: true })[0]; + // if exist _reconstructedRoute return that path instead of route.path + const customRoute = req._reconstructedRoute ? req._reconstructedRoute : undefined; + return extractPathForTransaction(req, { path: true, method: true, customRoute })[0]; } } } diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index a80e795fb9a6..935e24124b49 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -259,11 +259,11 @@ function makeWrappedRootLoader(remixVersion: number) { const traceAndBaggage = getTraceAndBaggage(); if (isDeferredData(res)) { - return { - ...res.data, - ...traceAndBaggage, - remixVersion, - }; + res.data['sentryTrace'] = traceAndBaggage.sentryTrace; + res.data['sentryBaggage'] = traceAndBaggage.sentryBaggage; + res.data['remixVersion'] = remixVersion; + + return res; } if (isResponse(res)) { diff --git a/packages/remix/test/integration/app_v1/routes/loader-defer-response/$id.tsx b/packages/remix/test/integration/app_v1/routes/loader-defer-response/$id.tsx new file mode 100644 index 000000000000..f0c19208a85f --- /dev/null +++ b/packages/remix/test/integration/app_v1/routes/loader-defer-response/$id.tsx @@ -0,0 +1,2 @@ +export * from '../../../common/routes/loader-defer-response.$id'; +export { default } from '../../../common/routes/loader-defer-response.$id'; diff --git a/packages/remix/test/integration/app_v1/routes/loader-defer-response/index.tsx b/packages/remix/test/integration/app_v1/routes/loader-defer-response/index.tsx deleted file mode 100644 index fd3a7b3f898d..000000000000 --- a/packages/remix/test/integration/app_v1/routes/loader-defer-response/index.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../../common/routes/loader-defer-response'; -export { default } from '../../../common/routes/loader-defer-response'; diff --git a/packages/remix/test/integration/app_v2/routes/loader-defer-response.$id.tsx b/packages/remix/test/integration/app_v2/routes/loader-defer-response.$id.tsx new file mode 100644 index 000000000000..69499e594ccc --- /dev/null +++ b/packages/remix/test/integration/app_v2/routes/loader-defer-response.$id.tsx @@ -0,0 +1,2 @@ +export * from '../../common/routes/loader-defer-response.$id'; +export { default } from '../../common/routes/loader-defer-response.$id'; diff --git a/packages/remix/test/integration/app_v2/routes/loader-defer-response.tsx b/packages/remix/test/integration/app_v2/routes/loader-defer-response.tsx deleted file mode 100644 index 38415a9a3781..000000000000 --- a/packages/remix/test/integration/app_v2/routes/loader-defer-response.tsx +++ /dev/null @@ -1,2 +0,0 @@ -export * from '../../common/routes/loader-defer-response'; -export { default } from '../../common/routes/loader-defer-response'; diff --git a/packages/remix/test/integration/common/routes/loader-defer-response.tsx b/packages/remix/test/integration/common/routes/loader-defer-response.$id.tsx similarity index 83% rename from packages/remix/test/integration/common/routes/loader-defer-response.tsx rename to packages/remix/test/integration/common/routes/loader-defer-response.$id.tsx index b55d8dfede47..ad7060888c19 100644 --- a/packages/remix/test/integration/common/routes/loader-defer-response.tsx +++ b/packages/remix/test/integration/common/routes/loader-defer-response.$id.tsx @@ -14,7 +14,7 @@ export default function LoaderJSONResponse() { return (
-

{data && data.id ? data.id : 'Not Found'}

+

{data && data.id ? data.id : 'Not Found'}

); } diff --git a/packages/remix/test/integration/test/client/deferred-response.test.ts b/packages/remix/test/integration/test/client/deferred-response.test.ts new file mode 100644 index 000000000000..c239565428da --- /dev/null +++ b/packages/remix/test/integration/test/client/deferred-response.test.ts @@ -0,0 +1,9 @@ +import { test, expect } from '@playwright/test'; + +test('should receive correct data from instrumented defer response', async ({ page }) => { + await page.goto('/loader-defer-response/98765'); + + const renderedId = await page.waitForSelector('#data-render'); + + expect(await renderedId?.textContent()).toBe('98765'); +}); diff --git a/packages/remix/test/integration/test/server/loader.test.ts b/packages/remix/test/integration/test/server/loader.test.ts index c3be005f0c1b..e45c5be33e8c 100644 --- a/packages/remix/test/integration/test/server/loader.test.ts +++ b/packages/remix/test/integration/test/server/loader.test.ts @@ -201,12 +201,12 @@ describe.each(['builtin', 'express'])('Remix API Loaders with adapter = %s', ada it('correctly instruments a deferred loader', async () => { const env = await RemixTestEnv.init(adapter); - const url = `${env.url}/loader-defer-response`; + const url = `${env.url}/loader-defer-response/123123`; const envelope = await env.getEnvelopeRequest({ url, envelopeType: 'transaction' }); const transaction = envelope[2]; assertSentryTransaction(transaction, { - transaction: useV2 ? 'routes/loader-defer-response' : 'root', + transaction: useV2 ? 'routes/loader-defer-response.$id' : 'routes/loader-defer-response/$id', transaction_info: { source: 'route', }, @@ -217,11 +217,11 @@ describe.each(['builtin', 'express'])('Remix API Loaders with adapter = %s', ada op: 'function.remix.loader', }, { - description: 'routes/loader-defer-response', + description: 'routes/loader-defer-response.$id', op: 'function.remix.loader', }, { - description: 'routes/loader-defer-response', + description: 'routes/loader-defer-response.$id', op: 'function.remix.document_request', }, ] @@ -231,11 +231,11 @@ describe.each(['builtin', 'express'])('Remix API Loaders with adapter = %s', ada op: 'function.remix.loader', }, { - description: 'routes/loader-defer-response/index', + description: 'routes/loader-defer-response/$id', op: 'function.remix.loader', }, { - description: 'root', + description: 'routes/loader-defer-response/$id', op: 'function.remix.document_request', }, ], diff --git a/packages/replay/.eslintignore b/packages/replay/.eslintignore index 3a347e0e1879..c76c6c2d64d1 100644 --- a/packages/replay/.eslintignore +++ b/packages/replay/.eslintignore @@ -1,3 +1,6 @@ node_modules/ build/ - +demo/build/ +# TODO: Check if we can re-introduce linting in demo +demo +metrics diff --git a/packages/tracing-internal/src/node/integrations/express.ts b/packages/tracing-internal/src/node/integrations/express.ts index 047236381269..4327f11c5ea7 100644 --- a/packages/tracing-internal/src/node/integrations/express.ts +++ b/packages/tracing-internal/src/node/integrations/express.ts @@ -64,6 +64,8 @@ type Layer = { handle_request: (req: PatchedRequest, res: ExpressResponse, next: () => void) => void; route?: { path: RouteType | RouteType[] }; path?: string; + regexp?: RegExp; + keys?: { name: string; offset: number; optional: boolean }[]; }; type RouteType = string | RegExp; @@ -318,7 +320,24 @@ function instrumentRouter(appOrRouter: ExpressRouter): void { } // Otherwise, the hardcoded path (i.e. a partial route without params) is stored in layer.path - const partialRoute = layerRoutePath || layer.path || ''; + let partialRoute; + + if (layerRoutePath) { + partialRoute = layerRoutePath; + } else { + /** + * prevent duplicate segment in _reconstructedRoute param if router match multiple routes before final path + * example: + * original url: /api/v1/1234 + * prevent: /api/api/v1/:userId + * router structure + * /api -> middleware + * /api/v1 -> middleware + * /1234 -> endpoint with param :userId + * final _reconstructedRoute is /api/v1/:userId + */ + partialRoute = preventDuplicateSegments(req.originalUrl, req._reconstructedRoute, layer.path) || ''; + } // Normalize the partial route so that it doesn't contain leading or trailing slashes // and exclude empty or '*' wildcard routes. @@ -370,6 +389,79 @@ type LayerRoutePathInfo = { numExtraSegments: number; }; +/** + * Recreate layer.route.path from layer.regexp and layer.keys. + * Works until express.js used package path-to-regexp@0.1.7 + * or until layer.keys contain offset attribute + * + * @param layer the layer to extract the stringified route from + * + * @returns string in layer.route.path structure 'router/:pathParam' or undefined + */ +export const extractOriginalRoute = ( + path?: Layer['path'], + regexp?: Layer['regexp'], + keys?: Layer['keys'], +): string | undefined => { + if (!path || !regexp || !keys || Object.keys(keys).length === 0 || !keys[0]?.offset) { + return undefined; + } + + const orderedKeys = keys.sort((a, b) => a.offset - b.offset); + + // add d flag for getting indices from regexp result + const pathRegex = new RegExp(regexp, `${regexp.flags}d`); + /** + * use custom type cause of TS error with missing indices in RegExpExecArray + */ + const execResult = pathRegex.exec(path) as (RegExpExecArray & { indices: [number, number][] }) | null; + + if (!execResult || !execResult.indices) { + return undefined; + } + /** + * remove first match from regex cause contain whole layer.path + */ + const [, ...paramIndices] = execResult.indices; + + if (paramIndices.length !== orderedKeys.length) { + return undefined; + } + let resultPath = path; + let indexShift = 0; + + /** + * iterate param matches from regexp.exec + */ + paramIndices.forEach(([startOffset, endOffset], index: number) => { + /** + * isolate part before param + */ + const substr1 = resultPath.substring(0, startOffset - indexShift); + /** + * define paramName as replacement in format :pathParam + */ + const replacement = `:${orderedKeys[index].name}`; + + /** + * isolate part after param + */ + const substr2 = resultPath.substring(endOffset - indexShift); + + /** + * recreate original path but with param replacement + */ + resultPath = substr1 + replacement + substr2; + + /** + * calculate new index shift after resultPath was modified + */ + indexShift = indexShift + (endOffset - startOffset - replacement.length); + }); + + return resultPath; +}; + /** * Extracts and stringifies the layer's route which can either be a string with parameters (`users/:id`), * a RegEx (`/test/`) or an array of strings and regexes (`['/path1', /\/path[2-5]/, /path/:id]`). Additionally @@ -382,11 +474,24 @@ type LayerRoutePathInfo = { * if the route was an array (defaults to 0). */ function getLayerRoutePathInfo(layer: Layer): LayerRoutePathInfo { - const lrp = layer.route?.path; + let lrp = layer.route?.path; const isRegex = isRegExp(lrp); const isArray = Array.isArray(lrp); + if (!lrp) { + // parse node.js major version + const [major] = process.versions.node.split('.').map(Number); + + // allow call extractOriginalRoute only if node version support Regex d flag, node 16+ + if (major >= 16) { + /** + * If lrp does not exist try to recreate original layer path from route regexp + */ + lrp = extractOriginalRoute(layer.path, layer.regexp, layer.keys); + } + } + if (!lrp) { return { isRegex, isArray, numExtraSegments: 0 }; } @@ -424,3 +529,28 @@ function getLayerRoutePathString(isArray: boolean, lrp?: RouteType | RouteType[] } return lrp && lrp.toString(); } + +/** + * remove duplicate segment contain in layerPath against reconstructedRoute, + * and return only unique segment that can be added into reconstructedRoute + */ +export function preventDuplicateSegments( + originalUrl?: string, + reconstructedRoute?: string, + layerPath?: string, +): string | undefined { + const originalUrlSplit = originalUrl?.split('/').filter(v => !!v); + let tempCounter = 0; + const currentOffset = reconstructedRoute?.split('/').filter(v => !!v).length || 0; + const result = layerPath + ?.split('/') + .filter(segment => { + if (originalUrlSplit?.[currentOffset + tempCounter] === segment) { + tempCounter += 1; + return true; + } + return false; + }) + .join('/'); + return result; +} diff --git a/packages/tracing-internal/test/node/express.test.ts b/packages/tracing-internal/test/node/express.test.ts new file mode 100644 index 000000000000..e9f4df236b33 --- /dev/null +++ b/packages/tracing-internal/test/node/express.test.ts @@ -0,0 +1,91 @@ +import { extractOriginalRoute, preventDuplicateSegments } from '../../src/node/integrations/express'; + +/** + * prevent duplicate segment in _reconstructedRoute param if router match multiple routes before final path + * example: + * original url: /api/v1/1234 + * prevent: /api/api/v1/:userId + * router structure + * /api -> middleware + * /api/v1 -> middleware + * /1234 -> endpoint with param :userId + * final _reconstructedRoute is /api/v1/:userId + */ +describe('unit Test for preventDuplicateSegments', () => { + it('should return api segment', () => { + const originalUrl = '/api/v1/1234'; + const reconstructedRoute = ''; + const layerPath = '/api'; + const result = preventDuplicateSegments(originalUrl, reconstructedRoute, layerPath); + expect(result).toBe('api'); + }); + + it('should prevent duplicate segment api', () => { + const originalUrl = '/api/v1/1234'; + const reconstructedRoute = '/api'; + const layerPath = '/api/v1'; + const result = preventDuplicateSegments(originalUrl, reconstructedRoute, layerPath); + expect(result).toBe('v1'); + }); + + it('should prevent duplicate segment v1', () => { + const originalUrl = '/api/v1/1234'; + const reconstructedRoute = '/api/v1'; + const layerPath = '/v1/1234'; + const result1 = preventDuplicateSegments(originalUrl, reconstructedRoute, layerPath); + expect(result1).toBe('1234'); + }); +}); +describe('preventDuplicateSegments should handle empty input gracefully', () => { + it('Empty input values', () => { + expect(preventDuplicateSegments()).toBeUndefined(); + }); + + it('Empty originalUrl', () => { + expect(preventDuplicateSegments('', '/api/v1/1234', '/api/api/v1/1234')).toBe(''); + }); + + it('Empty reconstructedRoute', () => { + expect(preventDuplicateSegments('/api/v1/1234', '', '/api/api/v1/1234')).toBe('api/v1/1234'); + }); + + it('Empty layerPath', () => { + expect(preventDuplicateSegments('/api/v1/1234', '/api/v1/1234', '')).toBe(''); + }); +}); + +// parse node.js major version +const [major] = process.versions.node.split('.').map(Number); +// Test this funciton only if node is 16+ because regex d flag is support from node 16+ +if (major >= 16) { + describe('extractOriginalRoute', () => { + it('should return undefined if path, regexp, or keys are missing', () => { + expect(extractOriginalRoute('/example')).toBeUndefined(); + expect(extractOriginalRoute('/example', /test/)).toBeUndefined(); + }); + + it('should return undefined if keys do not contain an offset property', () => { + const path = '/example'; + const regex = /example/; + const key = { name: 'param1', offset: 0, optional: false }; + expect(extractOriginalRoute(path, regex, [key])).toBeUndefined(); + }); + + it('should return the original route path when valid inputs are provided', () => { + const path = '/router/123'; + const regex = /^\/router\/(\d+)$/; + const keys = [{ name: 'pathParam', offset: 8, optional: false }]; + expect(extractOriginalRoute(path, regex, keys)).toBe('/router/:pathParam'); + }); + + it('should handle multiple parameters in the route', () => { + const path = '/user/42/profile/username'; + const regex = /^\/user\/(\d+)\/profile\/(\w+)$/; + const keys = [ + { name: 'userId', offset: 6, optional: false }, + { name: 'username', offset: 17, optional: false }, + ]; + expect(extractOriginalRoute(path, regex, keys)).toBe('/user/:userId/profile/:username'); + }); + }); +} diff --git a/packages/types/src/polymorphics.ts b/packages/types/src/polymorphics.ts index e6fcac8a682f..88abf7770b2c 100644 --- a/packages/types/src/polymorphics.ts +++ b/packages/types/src/polymorphics.ts @@ -75,4 +75,5 @@ type ExpressRequest = NodeRequest & { user?: { [key: string]: any; }; + _reconstructedRoute?: string; }; diff --git a/packages/utils/src/anr.ts b/packages/utils/src/anr.ts index b007c1355cb7..89990c3414f7 100644 --- a/packages/utils/src/anr.ts +++ b/packages/utils/src/anr.ts @@ -10,6 +10,8 @@ type WatchdogReturn = { enabled: (state: boolean) => void; }; +type CreateTimerImpl = () => { getTimeMs: () => number; reset: () => void }; + /** * A node.js watchdog timer * @param pollInterval The interval that we expect to get polled at @@ -17,14 +19,18 @@ type WatchdogReturn = { * @param callback The callback to call for ANR * @returns An object with `poll` and `enabled` functions {@link WatchdogReturn} */ -export function watchdogTimer(pollInterval: number, anrThreshold: number, callback: () => void): WatchdogReturn { - let lastPoll = process.hrtime(); +export function watchdogTimer( + createTimer: CreateTimerImpl, + pollInterval: number, + anrThreshold: number, + callback: () => void, +): WatchdogReturn { + const timer = createTimer(); let triggered = false; let enabled = true; setInterval(() => { - const [seconds, nanoSeconds] = process.hrtime(lastPoll); - const diffMs = Math.floor(seconds * 1e3 + nanoSeconds / 1e6); + const diffMs = timer.getTimeMs(); if (triggered === false && diffMs > pollInterval + anrThreshold) { triggered = true; @@ -40,7 +46,7 @@ export function watchdogTimer(pollInterval: number, anrThreshold: number, callba return { poll: () => { - lastPoll = process.hrtime(); + timer.reset(); }, enabled: (state: boolean) => { enabled = state; diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts index 8824cee77d66..fc0f855fd5d4 100644 --- a/scripts/node-unit-tests.ts +++ b/scripts/node-unit-tests.ts @@ -20,6 +20,7 @@ const DEFAULT_SKIP_TESTS_PACKAGES = [ '@sentry/angular', '@sentry/svelte', '@sentry/replay', + '@sentry-internal/feedback', '@sentry/wasm', '@sentry/bun', '@sentry/deno', diff --git a/yarn.lock b/yarn.lock index f64756e09026..e4e1855b1d18 100644 --- a/yarn.lock +++ b/yarn.lock @@ -759,7 +759,7 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.11.0", "@babel/generator@^7.20.1", "@babel/generator@^7.20.2", "@babel/generator@^7.7.2": +"@babel/generator@^7.11.0", "@babel/generator@^7.20.2", "@babel/generator@^7.7.2": version "7.20.4" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.4.tgz#4d9f8f0c30be75fd90a0562099a26e5839602ab8" integrity sha512-luCf7yk/cm7yab6CAW1aiFnmEfBJplb/JojV56MYEK7ziWfGmFlTfmL9Ehwfy4gFhbjBfWO1wj7/TuSbVNEEtA== @@ -1232,12 +1232,12 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.1", "@babel/parser@^7.14.7", "@babel/parser@^7.16.4", "@babel/parser@^7.18.10", "@babel/parser@^7.20.1", "@babel/parser@^7.20.2", "@babel/parser@^7.4.5", "@babel/parser@^7.7.0": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.10.4", "@babel/parser@^7.11.1", "@babel/parser@^7.14.7", "@babel/parser@^7.16.4", "@babel/parser@^7.18.10", "@babel/parser@^7.20.2", "@babel/parser@^7.4.5", "@babel/parser@^7.7.0": version "7.20.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.3.tgz#5358cf62e380cf69efcb87a7bb922ff88bfac6e2" integrity sha512-OP/s5a94frIPXwjzEcv5S/tpQfc6XhxYUnmWpgdqMWGgYCuErA3SzozaRAMQgSZWKeTJxht9aWAkUY+0UzvOFg== -"@babel/parser@^7.14.5", "@babel/parser@^7.14.8", "@babel/parser@^7.20.13", "@babel/parser@^7.20.7": +"@babel/parser@^7.14.5", "@babel/parser@^7.14.8", "@babel/parser@^7.20.7": version "7.20.15" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89" integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg== @@ -2411,42 +2411,10 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.11.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": - version "7.20.1" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.1.tgz#9b15ccbf882f6d107eeeecf263fbcdd208777ec8" - integrity sha512-d3tN8fkVJwFLkHkBN479SOsw4DMZnz8cdbL/gvuDuzy3TS6Nfw80HuQqhw1pITbIruHyh7d1fMA47kWzmcUEGA== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.1" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.1" - "@babel/types" "^7.20.0" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.14.8", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.7": - version "7.20.13" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.13.tgz#817c1ba13d11accca89478bd5481b2d168d07473" - integrity sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ== - dependencies: - "@babel/code-frame" "^7.18.6" - "@babel/generator" "^7.20.7" - "@babel/helper-environment-visitor" "^7.18.9" - "@babel/helper-function-name" "^7.19.0" - "@babel/helper-hoist-variables" "^7.18.6" - "@babel/helper-split-export-declaration" "^7.18.6" - "@babel/parser" "^7.20.13" - "@babel/types" "^7.20.7" - debug "^4.1.0" - globals "^11.1.0" - -"@babel/traverse@^7.22.10", "@babel/traverse@^7.23.0": - version "7.23.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.0.tgz#18196ddfbcf4ccea324b7f6d3ada00d8c5a99c53" - integrity sha512-t/QaEvyIoIkwzpiZ7aoSKK8kObQYeF7T2v+dazAYCb8SXtp58zEVkWW7zAnju8FNKNdr4ScAOEDmMItbyOmEYw== +"@babel/traverse@^7.11.0", "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.8", "@babel/traverse@^7.19.0", "@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.10", "@babel/traverse@^7.20.12", "@babel/traverse@^7.20.13", "@babel/traverse@^7.20.7", "@babel/traverse@^7.22.10", "@babel/traverse@^7.23.0", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": + version "7.23.2" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" + integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== dependencies: "@babel/code-frame" "^7.22.13" "@babel/generator" "^7.23.0" @@ -3076,6 +3044,11 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@fastify/busboy@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.0.0.tgz#f22824caff3ae506b18207bad4126dbc6ccdb6b8" + integrity sha512-JUFJad5lv7jxj926GPgymrWQxxjPYuJNiNjNMzqT+HiuP6Vl3dk5xzG+8sTX96np0ZAluvaMzPsjhHZ5rNuNQQ== + "@gar/promisify@^1.0.1", "@gar/promisify@^1.1.3": version "1.1.3" resolved "https://registry.yarnpkg.com/@gar/promisify/-/promisify-1.1.3.tgz#555193ab2e3bb3b6adc3d551c9c030d9e860daf6" @@ -4620,11 +4593,6 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== -"@polka/url@^1.0.0-next.9": - version "1.0.0-next.12" - resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.12.tgz#431ec342a7195622f86688bbda82e3166ce8cb28" - integrity sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ== - "@prisma/client@3.15.2": version "3.15.2" resolved "https://registry.yarnpkg.com/@prisma/client/-/client-3.15.2.tgz#2181398147afc79bfe0d83c03a88dc45b49bd365" @@ -5084,6 +5052,28 @@ proxy-from-env "^1.1.0" which "^2.0.2" +"@sentry/core@7.70.0": + version "7.70.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.70.0.tgz#c481ef27cf05293fb681ee4ff4d4b0b1e8664bb5" + integrity sha512-voUsGVM+jwRp99AQYFnRvr7sVd2tUhIMj1L6F42LtD3vp7t5ZnKp3NpXagtFW2vWzXESfyJUBhM0qI/bFvn7ZA== + dependencies: + "@sentry/types" "7.70.0" + "@sentry/utils" "7.70.0" + tslib "^2.4.1 || ^1.9.3" + +"@sentry/types@7.70.0": + version "7.70.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.70.0.tgz#c7b533bb18144e3b020550b38cf4812c32d05ffe" + integrity sha512-rY4DqpiDBtXSk4MDNBH3dwWqfPbNBI/9GA7Y5WJSIcObBtfBKp0fzYliHJZD0pgM7d4DPFrDn42K9Iiumgymkw== + +"@sentry/utils@7.70.0": + version "7.70.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.70.0.tgz#825387ceb10cbb1e145357394b697a1a6d60eb74" + integrity sha512-0cChMH0lsGp+5I3D4wOHWwjFN19HVrGUs7iWTLTO5St3EaVbdeLbI1vFXHxMxvopbwgpeZafbreHw/loIdZKpw== + dependencies: + "@sentry/types" "7.70.0" + tslib "^2.4.1 || ^1.9.3" + "@sentry/vite-plugin@^0.6.1": version "0.6.1" resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-0.6.1.tgz#31eb744e8d87b1528eed8d41433647727a62e7c0" @@ -5219,37 +5209,29 @@ resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@size-limit/file@4.10.1": - version "4.10.1" - resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-4.10.1.tgz#e5cb204edf4974b1cad6a6eb0b639dcf6b48091b" - integrity sha512-3+h+CfrbIeTodPNpjJgs+jQoXYhAIAuK3HN9j5hdV9H6Q84hshbdn/TMYMIQuVQm9JodPk6l2G1wDd6LqweKHQ== +"@size-limit/esbuild@9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@size-limit/esbuild/-/esbuild-9.0.0.tgz#08e0138d01e8a693e0d7ed274ac36b4065afe814" + integrity sha512-y3NY0inaFeLqV6SRXNVILhawQdQcODxF30qft6OalsrqtQtBjt++6ZeahYhUbrVexUEWRh6c7yPCe8RvHn8hlA== dependencies: - semver "7.3.4" + esbuild "^0.19.2" + nanoid "^3.3.6" -"@size-limit/preset-small-lib@^4.5.5": - version "4.10.1" - resolved "https://registry.yarnpkg.com/@size-limit/preset-small-lib/-/preset-small-lib-4.10.1.tgz#ad154b5760410c089816245490743a74e8300608" - integrity sha512-a1hUwbKiPy5INmWXiJFUUy6N7vFHGNecAkrwCxQbsjRn3rkA2qqt5JHJRpzFCBpynQBR1qA5OtQNd2fN2VKB/Q== +"@size-limit/file@9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@size-limit/file/-/file-9.0.0.tgz#eed5415f5bcc8407979e47ffa49ffaf12d2d2378" + integrity sha512-oM2UaH2FRq4q22k+R+P6xCpzET10T94LFdSjb9svVu/vOD7NaB9LGcG6se8TW1BExXiyXO4GEhLsBt3uMKM3qA== dependencies: - "@size-limit/file" "4.10.1" - "@size-limit/webpack" "4.10.1" + semver "7.5.4" -"@size-limit/webpack@4.10.1": - version "4.10.1" - resolved "https://registry.yarnpkg.com/@size-limit/webpack/-/webpack-4.10.1.tgz#c1d8cc7f7e387b76944485c8416cf0a11894cccc" - integrity sha512-BZMl/FrJVgKuXen9gGf+5MspDzvAQWl+cEDYdzvf7JQb7e/oiH89ACLQJsWY1Q4f+j0N9U5XlDMHdsHVdNdp5w== +"@size-limit/preset-small-lib@~9.0.0": + version "9.0.0" + resolved "https://registry.yarnpkg.com/@size-limit/preset-small-lib/-/preset-small-lib-9.0.0.tgz#cbac7f3460fb4fac935d0f39a5757864f627f4e2" + integrity sha512-nkbZxn12pTpABYVyX5nsjQuLFpn8wDmd2XKoq/MiqKOc3ocz5BBwXTruqTL5ZKDW1OxEAWZMQlxf2kg3kY3X1Q== dependencies: - css-loader "^5.1.1" - escape-string-regexp "^4.0.0" - file-loader "^6.2.0" - mkdirp "^1.0.4" - nanoid "^3.1.20" - optimize-css-assets-webpack-plugin "^5.0.4" - pnp-webpack-plugin "^1.6.4" - rimraf "^3.0.2" - style-loader "^2.0.0" - webpack "^4.44.1" - webpack-bundle-analyzer "^4.4.0" + "@size-limit/esbuild" "9.0.0" + "@size-limit/file" "9.0.0" + size-limit "9.0.0" "@socket.io/base64-arraybuffer@~1.0.2": version "1.0.2" @@ -6206,7 +6188,7 @@ dependencies: "@types/node" "*" -"@types/resolve@^1.17.0": +"@types/resolve@1.20.3", "@types/resolve@^1.17.0": version "1.20.3" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.3.tgz#066742d69a0bbba8c5d7d517f82e1140ddeb3c3c" integrity sha512-NH5oErHOtHZYcjCtg69t26aXEk4BN2zLWqf7wnDZ+dpe0iR7Rds1SPGEItl3fca21oOe0n3OCnZ4W7jBxu7FOw== @@ -7124,7 +7106,7 @@ acorn-walk@^7.1.1: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn-walk@^8.0.0, acorn-walk@^8.1.1, acorn-walk@^8.2.0: +acorn-walk@^8.1.1, acorn-walk@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== @@ -7144,16 +7126,16 @@ acorn@^7.1.1, acorn@^7.4.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7.1: - version "8.8.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" - integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== - acorn@^8.10.0: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.2.4, acorn@^8.4.1, acorn@^8.5.0, acorn@^8.7.0, acorn@^8.7.1: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + add-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" @@ -9961,6 +9943,11 @@ byte-size@8.1.1: resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-8.1.1.tgz#3424608c62d59de5bfda05d31e0313c6174842ae" integrity sha512-tUkzZWK0M/qdoLEqikxBWe4kumyuwjl3HO6zHTr4yEI23EojPtLYXdG1+AQY7MN0cGyNDvEaJ8wiYQm6P2bPxg== +bytes-iec@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/bytes-iec/-/bytes-iec-3.1.1.tgz#94cd36bf95c2c22a82002c247df8772d1d591083" + integrity sha512-fey6+4jDK7TFtFg/klGSvNKJctyU7n2aQdnM+CO0ruLPbqqMOM8Tio0Pc+deqUeVKX1tL5DQep1zQ7+37aTAsA== + bytes@1: version "1.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-1.0.0.tgz#3569ede8ba34315fab99c3e92cb04c7220de1fa8" @@ -9976,7 +9963,7 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -bytes@3.1.2, bytes@^3.1.0: +bytes@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== @@ -10529,11 +10516,6 @@ ci-info@^3.8.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== -ci-job-number@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/ci-job-number/-/ci-job-number-1.2.2.tgz#f4e5918fcaeeda95b604f214be7d7d4a961fe0c0" - integrity sha512-CLOGsVDrVamzv8sXJGaILUVI6dsuAkouJP/n6t+OxLPeeA4DDby7zn9SB6EUpa1H7oIKoE+rMmkW80zYsFfUjA== - cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -10960,7 +10942,7 @@ commander@^4.0.0, commander@^4.1.1: resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA== -commander@^6.0.0, commander@^6.2.0, commander@^6.2.1: +commander@^6.0.0, commander@^6.2.1: version "6.2.1" resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== @@ -11572,7 +11554,7 @@ css-loader@6.2.0: postcss-value-parser "^4.1.0" semver "^7.3.5" -css-loader@^5.1.1, css-loader@^5.2.0: +css-loader@^5.2.0: version "5.2.7" resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.2.7.tgz#9b9f111edf6fb2be5dc62525644cbc9c232064ae" integrity sha512-Q7mOvpBNBG7YrVGMxRxcBJZFL75o+cH2abNASdibkj/fffYD8qWbInZrD0S9ccI6vZclF3DsHE7njGlLtaHbhg== @@ -11832,7 +11814,7 @@ cssnano-utils@^3.1.0: resolved "https://registry.yarnpkg.com/cssnano-utils/-/cssnano-utils-3.1.0.tgz#95684d08c91511edfc70d2636338ca37ef3a6861" integrity sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA== -cssnano@4.1.10, cssnano@^4.1.10: +cssnano@4.1.10: version "4.1.10" resolved "https://registry.yarnpkg.com/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2" integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ== @@ -12679,7 +12661,7 @@ duplexer3@^0.1.4: resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI= -duplexer@^0.1.1, duplexer@^0.1.2, duplexer@~0.1.1: +duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== @@ -14868,14 +14850,6 @@ file-loader@6.0.0: loader-utils "^2.0.0" schema-utils "^2.6.5" -file-loader@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" - integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== - dependencies: - loader-utils "^2.0.0" - schema-utils "^3.0.0" - file-uri-to-path@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" @@ -15999,7 +15973,7 @@ globby@10.0.0: merge2 "^1.2.3" slash "^3.0.0" -globby@11.1.0, globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.1.0: +globby@11.1.0, globby@^11.0.1, globby@^11.0.3, globby@^11.1.0: version "11.1.0" resolved "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== @@ -16207,13 +16181,6 @@ gud@^1.0.0: resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== -gzip-size@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-6.0.0.tgz#065367fd50c239c0671cbcbad5be3e2eeb10e462" - integrity sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q== - dependencies: - duplexer "^0.1.2" - handle-thing@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.1.tgz#857f79ce359580c340d43081cc648970d0bb234e" @@ -19225,14 +19192,6 @@ language-tags@^1.0.5: dependencies: language-subtag-registry "~0.3.2" -last-call-webpack-plugin@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/last-call-webpack-plugin/-/last-call-webpack-plugin-3.0.0.tgz#9742df0e10e3cf46e5c0381c2de90d3a7a2d7555" - integrity sha512-7KI2l2GIZa9p2spzPIVZBYyNKkN+e/SQPpnjlTiPhdbDW3F86tdKKELxKpzJ5sgU19wQWsACULZmpTPYHeWO5w== - dependencies: - lodash "^4.17.5" - webpack-sources "^1.1.0" - latest-version@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/latest-version/-/latest-version-5.1.0.tgz#119dfe908fe38d15dfa43ecd13fa12ec8832face" @@ -19469,16 +19428,16 @@ lie@3.1.1: dependencies: immediate "~3.0.5" -lilconfig@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.2.tgz#9f802752254697d22a5c33e88d97b7329008c060" - integrity sha512-4zUThttj8TQ4N7Pps92Z79jPf1OMcll4m61pivQSVk5MT78hVhNa2LrKTuNYD0AGLpmpf7zeIKOxSt6hHBfypw== - lilconfig@^2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== +lilconfig@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" + integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== + line-column@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/line-column/-/line-column-1.0.2.tgz#d25af2936b6f4849172b312e4792d1d987bc34a2" @@ -19876,7 +19835,7 @@ lodash.uniqby@^4.7.0: resolved "https://registry.yarnpkg.com/lodash.uniqby/-/lodash.uniqby-4.7.0.tgz#d99c07a669e9e6d24e1362dfe266c67616af1302" integrity sha1-2ZwHpmnp5tJOE2Lf4mbGdhavEwI= -lodash@4.17.21, lodash@^4.17.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.7.0: +lodash@4.17.21, lodash@^4.17.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -21560,7 +21519,7 @@ nan@^2.12.1: resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== -nanoid@^3.1.16, nanoid@^3.1.20, nanoid@^3.1.23: +nanoid@^3.1.16, nanoid@^3.1.23: version "3.3.4" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== @@ -21587,6 +21546,13 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" +nanospinner@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/nanospinner/-/nanospinner-1.1.0.tgz#d17ff621cb1784b0a206b400da88a0ef6db39b97" + integrity sha512-yFvNYMig4AthKYfHFl1sLj7B2nkHL4lzdig4osvl9/LdGbXwrdFRoqBS98gsEsOakr0yH+r5NZ/1Y9gdVB8trA== + dependencies: + picocolors "^1.0.0" + napi-build-utils@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/napi-build-utils/-/napi-build-utils-1.0.2.tgz#b1fddc0b2c46e380a0b7a76f984dd47c41a13806" @@ -22761,11 +22727,6 @@ open@^8.4.0: is-docker "^2.1.1" is-wsl "^2.2.0" -opener@^1.5.2: - version "1.5.2" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" - integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A== - opentelemetry-instrumentation-fetch-node@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/opentelemetry-instrumentation-fetch-node/-/opentelemetry-instrumentation-fetch-node-1.1.0.tgz#f51d79862390f3a694fa91c35c4383e037a04c11" @@ -22782,14 +22743,6 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optimize-css-assets-webpack-plugin@^5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/optimize-css-assets-webpack-plugin/-/optimize-css-assets-webpack-plugin-5.0.4.tgz#85883c6528aaa02e30bbad9908c92926bb52dc90" - integrity sha512-wqd6FdI2a5/FdoiCNNkEvLeA//lHHfG24Ln2Xm2qqdIk4aOlsR18jwpyOihqQ8849W3qu2DX8fOYxpvTMj+93A== - dependencies: - cssnano "^4.1.10" - last-call-webpack-plugin "^3.0.0" - optional-require@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/optional-require/-/optional-require-1.1.8.tgz#16364d76261b75d964c482b2406cb824d8ec44b7" @@ -23776,7 +23729,7 @@ pluralize@^8.0.0: resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== -pnp-webpack-plugin@1.6.4, pnp-webpack-plugin@^1.6.4: +pnp-webpack-plugin@1.6.4: version "1.6.4" resolved "https://registry.yarnpkg.com/pnp-webpack-plugin/-/pnp-webpack-plugin-1.6.4.tgz#c9711ac4dc48a685dabafc86f8b6dd9f8df84149" integrity sha512-7Wjy+9E3WwLOEL30D+m8TSTF7qJJUJLONBnwQp0518siuMxUQUbgZwssaFX+QKlZkjHZcw/IpZCt/H0srrntSg== @@ -26364,6 +26317,15 @@ resolve@1.20.0: is-core-module "^2.2.0" path-parse "^1.0.6" +resolve@1.22.8: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.11.1, resolve@^1.12.0, resolve@^1.13.1, resolve@^1.14.2, resolve@^1.17.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.0, resolve@^1.22.1, resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.8.1: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" @@ -27013,13 +26975,6 @@ semver@7.3.2: resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== -semver@7.3.4: - version "7.3.4" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97" - integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw== - dependencies: - lru-cache "^6.0.0" - semver@7.3.5: version "7.3.5" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7" @@ -27034,7 +26989,7 @@ semver@7.5.3: dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: +semver@7.5.4, semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.1.3, semver@^7.2.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.1, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -27345,15 +27300,6 @@ sinon@^7.3.2: nise "^1.5.2" supports-color "^5.5.0" -sirv@^1.0.7: - version "1.0.11" - resolved "https://registry.yarnpkg.com/sirv/-/sirv-1.0.11.tgz#81c19a29202048507d6ec0d8ba8910fda52eb5a4" - integrity sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg== - dependencies: - "@polka/url" "^1.0.0-next.9" - mime "^2.3.1" - totalist "^1.0.0" - sirv@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.2.tgz#128b9a628d77568139cff85703ad5497c46a4760" @@ -27368,19 +27314,17 @@ sisteransi@^1.0.5: resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed" integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg== -size-limit@^4.5.5: - version "4.10.1" - resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-4.10.1.tgz#b34e5047e1f08bb2941045b40d13739b2b9e5369" - integrity sha512-PEnh88PBmG/Kp6U/IR64Judu/smaq33lEDXf0sJiF1+tHwyrrSEWQkraFLbWKUI6cxT0weXuaTlh1sKYY7EK9Q== +size-limit@9.0.0, size-limit@~9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/size-limit/-/size-limit-9.0.0.tgz#203c47303462a8351976eb26175acea5f4e80447" + integrity sha512-DrA7o2DeRN3s+vwCA9nn7Ck9Y4pn9t0GNUwQRpKqBtBmNkl6LA2s/NlNCdtKHrEkRTeYA1ZQ65mnYveo9rUqgA== dependencies: - bytes "^3.1.0" - chokidar "^3.5.1" - ci-job-number "^1.2.2" - colorette "^1.2.2" - globby "^11.0.2" - lilconfig "^2.0.2" - ora "^5.3.0" - read-pkg-up "^7.0.1" + bytes-iec "^3.1.1" + chokidar "^3.5.3" + globby "^11.1.0" + lilconfig "^2.1.0" + nanospinner "^1.1.0" + picocolors "^1.0.0" skip-regex@^1.0.2: version "1.0.2" @@ -29252,11 +29196,6 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -totalist@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df" - integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g== - totalist@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd" @@ -29745,11 +29684,11 @@ undici@5.20.0: busboy "^1.6.0" undici@^5.21.0: - version "5.21.0" - resolved "https://registry.yarnpkg.com/undici/-/undici-5.21.0.tgz#b00dfc381f202565ab7f52023222ab862bb2494f" - integrity sha512-HOjK8l6a57b2ZGXOcUsI5NLfoTrfmbOl90ixJDl0AEFG4wgHNDQxtZy15/ZQp7HhjkpaGlp/eneMgtsu1dIlUA== + version "5.26.2" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.2.tgz#fa61bfe40f732540d15e58b0c1271872d8e3c995" + integrity sha512-a4PDLQgLTPHVzOK+x3F79/M4GtyYPl+aX9AAK7aQxpwxDwCqkeZCScy7Gk5kWT3JtdFq1uhO3uZJdLtHI4dK9A== dependencies: - busboy "^1.6.0" + "@fastify/busboy" "^2.0.0" unherit@^3.0.0: version "3.0.1" @@ -30686,21 +30625,6 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== -webpack-bundle-analyzer@^4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz#74013106e7e2b07cbd64f3a5ae847f7e814802c7" - integrity sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g== - dependencies: - acorn "^8.0.4" - acorn-walk "^8.0.0" - chalk "^4.1.0" - commander "^6.2.0" - gzip-size "^6.0.0" - lodash "^4.17.20" - opener "^1.5.2" - sirv "^1.0.7" - ws "^7.3.1" - webpack-dev-middleware@3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.7.2.tgz#0019c3db716e3fa5cecbf64f2ab88a74bab331f3" @@ -30927,35 +30851,6 @@ webpack@5.50.0: watchpack "^2.2.0" webpack-sources "^3.2.0" -webpack@^4.44.1: - version "4.46.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" - integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q== - dependencies: - "@webassemblyjs/ast" "1.9.0" - "@webassemblyjs/helper-module-context" "1.9.0" - "@webassemblyjs/wasm-edit" "1.9.0" - "@webassemblyjs/wasm-parser" "1.9.0" - acorn "^6.4.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.5.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.3" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.3" - watchpack "^1.7.4" - webpack-sources "^1.4.1" - webpack@^4.47.0: version "4.47.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.47.0.tgz#8b8a02152d7076aeb03b61b47dad2eeed9810ebc" @@ -31400,7 +31295,7 @@ ws@^6.2.1: dependencies: async-limiter "~1.0.0" -ws@^7.3.1, ws@^7.4.6: +ws@^7.4.6: version "7.5.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==