diff --git a/packages/react/package.json b/packages/react/package.json index 678bf828845f..0196251f2522 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -32,6 +32,7 @@ "@testing-library/react-hooks": "^3.3.0", "@types/hoist-non-react-statics": "^3.3.1", "@types/react": "^16.9.35", + "@types/react-router-3": "npm:@types/react-router@^3.2.0", "jest": "^24.7.1", "jsdom": "^16.2.2", "npm-run-all": "^4.1.2", @@ -39,6 +40,7 @@ "prettier-check": "^2.0.0", "react": "^16.0.0", "react-dom": "^16.0.0", + "react-router-3": "npm:react-router@^3.2.0", "react-test-renderer": "^16.13.1", "redux": "^4.0.5", "rimraf": "^2.6.3", diff --git a/packages/react/src/index.ts b/packages/react/src/index.ts index 84c7345fe66e..22ce1ce6e8d8 100644 --- a/packages/react/src/index.ts +++ b/packages/react/src/index.ts @@ -26,5 +26,6 @@ export * from '@sentry/browser'; export { Profiler, withProfiler, useProfiler } from './profiler'; export { ErrorBoundary, withErrorBoundary } from './errorboundary'; export { createReduxEnhancer } from './redux'; +export { reactRouterV3Instrumentation } from './reactrouter'; createReactEventProcessor(); diff --git a/packages/react/src/reactrouter.tsx b/packages/react/src/reactrouter.tsx new file mode 100644 index 000000000000..86ee8302ce9b --- /dev/null +++ b/packages/react/src/reactrouter.tsx @@ -0,0 +1,139 @@ +import { Transaction, TransactionContext } from '@sentry/types'; +import { getGlobalObject } from '@sentry/utils'; + +type ReactRouterInstrumentation = ( + startTransaction: (context: TransactionContext) => T | undefined, + startTransactionOnPageLoad?: boolean, + startTransactionOnLocationChange?: boolean, +) => void; + +// Many of the types below had to be mocked out to prevent typescript issues +// these types are required for correct functionality. + +export type Route = { path?: string; childRoutes?: Route[] }; + +export type Match = ( + props: { location: Location; routes: Route[] }, + cb: (error?: Error, _redirectLocation?: Location, renderProps?: { routes?: Route[] }) => void, +) => void; + +type Location = { + pathname: string; + action?: 'PUSH' | 'REPLACE' | 'POP'; +} & Record; + +type History = { + location?: Location; + listen?(cb: (location: Location) => void): void; +} & Record; + +const global = getGlobalObject(); + +/** + * Creates routing instrumentation for React Router v3 + * Works for React Router >= 3.2.0 and < 4.0.0 + * + * @param history object from the `history` library + * @param routes a list of all routes, should be + * @param match `Router.match` utility + */ +export function reactRouterV3Instrumentation( + history: History, + routes: Route[], + match: Match, +): ReactRouterInstrumentation { + return ( + startTransaction: (context: TransactionContext) => Transaction | undefined, + startTransactionOnPageLoad: boolean = true, + startTransactionOnLocationChange: boolean = true, + ) => { + let activeTransaction: Transaction | undefined; + let prevName: string | undefined; + + if (startTransactionOnPageLoad && global && global.location) { + // Have to use global.location because history.location might not be defined. + prevName = normalizeTransactionName(routes, global.location, match); + activeTransaction = startTransaction({ + name: prevName, + op: 'pageload', + tags: { + 'routing.instrumentation': 'react-router-v3', + }, + }); + } + + if (startTransactionOnLocationChange && history.listen) { + history.listen(location => { + if (location.action === 'PUSH') { + if (activeTransaction) { + activeTransaction.finish(); + } + const tags: Record = { 'routing.instrumentation': 'react-router-v3' }; + if (prevName) { + tags.from = prevName; + } + + prevName = normalizeTransactionName(routes, location, match); + activeTransaction = startTransaction({ + name: prevName, + op: 'navigation', + tags, + }); + } + }); + } + }; +} + +/** + * Normalize transaction names using `Router.match` + */ +function normalizeTransactionName(appRoutes: Route[], location: Location, match: Match): string { + let name = location.pathname; + match( + { + location, + routes: appRoutes, + }, + (error, _redirectLocation, renderProps) => { + if (error || !renderProps) { + return name; + } + + const routePath = getRouteStringFromRoutes(renderProps.routes || []); + if (routePath.length === 0 || routePath === '/*') { + return name; + } + + name = routePath; + return name; + }, + ); + return name; +} + +/** + * Generate route name from array of routes + */ +function getRouteStringFromRoutes(routes: Route[]): string { + if (!Array.isArray(routes) || routes.length === 0) { + return ''; + } + + const routesWithPaths: Route[] = routes.filter((route: Route) => !!route.path); + + let index = -1; + for (let x = routesWithPaths.length - 1; x >= 0; x--) { + const route = routesWithPaths[x]; + if (route.path && route.path.startsWith('/')) { + index = x; + break; + } + } + + return routesWithPaths + .slice(index) + .filter(({ path }) => !!path) + .map(({ path }) => path) + .join(''); +} diff --git a/packages/react/test/reactrouterv3.test.tsx b/packages/react/test/reactrouterv3.test.tsx new file mode 100644 index 000000000000..eeab53a00f74 --- /dev/null +++ b/packages/react/test/reactrouterv3.test.tsx @@ -0,0 +1,147 @@ +import { render } from '@testing-library/react'; +import * as React from 'react'; +import { createMemoryHistory, createRoutes, IndexRoute, match, Route, Router } from 'react-router-3'; + +import { Match, reactRouterV3Instrumentation, Route as RouteType } from '../src/reactrouter'; + +// Have to manually set types because we are using package-alias +declare module 'react-router-3' { + type History = { replace: Function; push: Function }; + export function createMemoryHistory(): History; + export const Router: React.ComponentType<{ history: History }>; + export const Route: React.ComponentType<{ path: string; component?: React.ComponentType }>; + export const IndexRoute: React.ComponentType<{ component: React.ComponentType }>; + export const match: Match; + export const createRoutes: (routes: any) => RouteType[]; +} + +describe('React Router V3', () => { + const routes = ( +
{children}
}> +
Home
} /> +
About
} /> +
Features
} /> + }) =>
{params.userid}
} + /> + +
OrgId
} /> +
Team
} /> +
+
+ ); + const history = createMemoryHistory(); + + const instrumentationRoutes = createRoutes(routes); + const instrumentation = reactRouterV3Instrumentation(history, instrumentationRoutes, match); + + it('starts a pageload transaction when instrumentation is started', () => { + const mockStartTransaction = jest.fn(); + instrumentation(mockStartTransaction); + expect(mockStartTransaction).toHaveBeenCalledTimes(1); + expect(mockStartTransaction).toHaveBeenLastCalledWith({ + name: '/', + op: 'pageload', + tags: { 'routing.instrumentation': 'react-router-v3' }, + }); + }); + + it('does not start pageload transaction if option is false', () => { + const mockStartTransaction = jest.fn(); + instrumentation(mockStartTransaction, false); + expect(mockStartTransaction).toHaveBeenCalledTimes(0); + }); + + it('starts a navigation transaction', () => { + const mockStartTransaction = jest.fn(); + instrumentation(mockStartTransaction); + render({routes}); + + history.push('/about'); + expect(mockStartTransaction).toHaveBeenCalledTimes(2); + expect(mockStartTransaction).toHaveBeenLastCalledWith({ + name: '/about', + op: 'navigation', + tags: { from: '/', 'routing.instrumentation': 'react-router-v3' }, + }); + + history.push('/features'); + expect(mockStartTransaction).toHaveBeenCalledTimes(3); + expect(mockStartTransaction).toHaveBeenLastCalledWith({ + name: '/features', + op: 'navigation', + tags: { from: '/about', 'routing.instrumentation': 'react-router-v3' }, + }); + }); + + it('does not start a transaction if option is false', () => { + const mockStartTransaction = jest.fn(); + instrumentation(mockStartTransaction, true, false); + render({routes}); + expect(mockStartTransaction).toHaveBeenCalledTimes(1); + }); + + it('only starts a navigation transaction on push', () => { + const mockStartTransaction = jest.fn(); + instrumentation(mockStartTransaction); + render({routes}); + + history.replace('hello'); + expect(mockStartTransaction).toHaveBeenCalledTimes(1); + }); + + it('finishes a transaction on navigation', () => { + const mockFinish = jest.fn(); + const mockStartTransaction = jest.fn().mockReturnValue({ finish: mockFinish }); + instrumentation(mockStartTransaction); + render({routes}); + expect(mockStartTransaction).toHaveBeenCalledTimes(1); + + history.push('/features'); + expect(mockFinish).toHaveBeenCalledTimes(1); + expect(mockStartTransaction).toHaveBeenCalledTimes(2); + }); + + it('normalizes transaction names', () => { + const mockStartTransaction = jest.fn(); + instrumentation(mockStartTransaction); + const { container } = render({routes}); + + history.push('/users/123'); + expect(container.innerHTML).toContain('123'); + + expect(mockStartTransaction).toHaveBeenCalledTimes(2); + expect(mockStartTransaction).toHaveBeenLastCalledWith({ + name: '/users/:userid', + op: 'navigation', + tags: { from: '/', 'routing.instrumentation': 'react-router-v3' }, + }); + }); + + it('normalizes nested transaction names', () => { + const mockStartTransaction = jest.fn(); + instrumentation(mockStartTransaction); + const { container } = render({routes}); + + history.push('/organizations/1234/v1/758'); + expect(container.innerHTML).toContain('Team'); + + expect(mockStartTransaction).toHaveBeenCalledTimes(2); + expect(mockStartTransaction).toHaveBeenLastCalledWith({ + name: '/organizations/:orgid/v1/:teamid', + op: 'navigation', + tags: { from: '/', 'routing.instrumentation': 'react-router-v3' }, + }); + + history.push('/organizations/543'); + expect(container.innerHTML).toContain('OrgId'); + + expect(mockStartTransaction).toHaveBeenCalledTimes(3); + expect(mockStartTransaction).toHaveBeenLastCalledWith({ + name: '/organizations/:orgid', + op: 'navigation', + tags: { from: '/organizations/:orgid/v1/:teamid', 'routing.instrumentation': 'react-router-v3' }, + }); + }); +}); diff --git a/yarn.lock b/yarn.lock index 02b2a131b32f..5ab09de45e0d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1458,6 +1458,11 @@ resolved "https://registry.yarnpkg.com/@types/highlight.js/-/highlight.js-9.12.4.tgz#8c3496bd1b50cc04aeefd691140aa571d4dbfa34" integrity sha512-t2szdkwmg2JJyuCM20e8kR2X59WCE5Zkl4bzm1u1Oukjm79zpbiAv+QjnwLnuuV0WHEcX2NgUItu0pAMKuOPww== +"@types/history@^3": + version "3.2.4" + resolved "https://registry.yarnpkg.com/@types/history/-/history-3.2.4.tgz#0b6c62240d1fac020853aa5608758991d9f6ef3d" + integrity sha512-q7x8QeCRk2T6DR2UznwYW//mpN5uNlyajkewH2xd1s1ozCS4oHFRg2WMusxwLFlE57EkUYsd/gCapLBYzV3ffg== + "@types/hoist-non-react-statics@^3.3.1": version "3.3.1" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f" @@ -1594,6 +1599,14 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c" integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA== +"@types/react-router-3@npm:@types/react-router@^3.2.0": + version "3.0.20" + resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-3.0.20.tgz#a711682475ccef70ad9ad9e459859380221e6ee6" + integrity sha512-0sx2ThGYgblXPf8we/c+umFzP3RCbBp1bbFmd3pO1UaOYnTDno82iql3MQTVqB09rhopKORNfakDU/9xZ4QR6g== + dependencies: + "@types/history" "^3" + "@types/react" "*" + "@types/react-test-renderer@*": version "16.9.2" resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-16.9.2.tgz#e1c408831e8183e5ad748fdece02214a7c2ab6c5" @@ -1942,11 +1955,32 @@ after@0.8.2: resolved "https://registry.yarnpkg.com/after/-/after-0.8.2.tgz#fedb394f9f0e02aa9768e702bda23b505fae7e1f" integrity sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8= -agent-base@4, agent-base@5, agent-base@6, agent-base@^4.3.0, agent-base@~4.2.1: +agent-base@4, agent-base@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" + integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== + dependencies: + es6-promisify "^5.0.0" + +agent-base@5: version "5.1.1" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-5.1.1.tgz#e8fb3f242959db44d63be665db7a8e739537a32c" integrity sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g== +agent-base@6: + version "6.0.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" + integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== + dependencies: + debug "4" + +agent-base@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + agentkeepalive@^3.4.1: version "3.5.2" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.2.tgz#a113924dd3fa24a0bc3b78108c450c2abee00f67" @@ -2225,7 +2259,7 @@ arrify@^2.0.0, arrify@^2.0.1: resolved "https://registry.yarnpkg.com/arrify/-/arrify-2.0.1.tgz#c9655e9331e0abcd588d2a7cad7e9956f66701fa" integrity sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug== -asap@^2.0.0: +asap@^2.0.0, asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= @@ -4172,6 +4206,11 @@ core-js-pure@^3.0.0: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" integrity sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA== +core-js@^1.0.0: + version "1.2.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" + integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY= + core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5: version "2.6.11" resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.11.tgz#38831469f9922bded8ee21c9dc46985e0399308c" @@ -4241,6 +4280,15 @@ create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: safe-buffer "^5.0.1" sha.js "^2.4.8" +create-react-class@^15.5.1: + version "15.6.3" + resolved "https://registry.yarnpkg.com/create-react-class/-/create-react-class-15.6.3.tgz#2d73237fb3f970ae6ebe011a9e66f46dbca80036" + integrity sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg== + dependencies: + fbjs "^0.8.9" + loose-envify "^1.3.1" + object-assign "^4.1.1" + cross-spawn-async@^2.1.1: version "2.2.5" resolved "https://registry.yarnpkg.com/cross-spawn-async/-/cross-spawn-async-2.2.5.tgz#845ff0c0834a3ded9d160daca6d390906bb288cc" @@ -5122,6 +5170,18 @@ es6-object-assign@^1.1.0: resolved "https://registry.yarnpkg.com/es6-object-assign/-/es6-object-assign-1.1.0.tgz#c2c3582656247c39ea107cb1e6652b6f9f24523c" integrity sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw= +es6-promise@^4.0.3: + version "4.2.8" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" + integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + escalade@^3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" @@ -5513,6 +5573,19 @@ fb-watchman@^2.0.0: dependencies: bser "2.1.1" +fbjs@^0.8.9: + version "0.8.17" + resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" + integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90= + dependencies: + core-js "^1.0.0" + isomorphic-fetch "^2.1.1" + loose-envify "^1.0.0" + object-assign "^4.1.0" + promise "^7.1.1" + setimmediate "^1.0.5" + ua-parser-js "^0.7.18" + fd-slicer@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" @@ -6312,6 +6385,16 @@ highlight.js@^9.13.1, highlight.js@^9.15.6: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-9.18.1.tgz#ed21aa001fe6252bb10a3d76d47573c6539fe13c" integrity sha512-OrVKYz70LHsnCgmbXctv/bfuvntIKDz177h0Co37DQ5jamGZLVmoCVMtjMtNZY3X9DrCcKfklHPNeA0uPZhSJg== +history@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/history/-/history-3.3.0.tgz#fcedcce8f12975371545d735461033579a6dae9c" + integrity sha1-/O3M6PEpdTcVRdc1RhAzV5ptrpw= + dependencies: + invariant "^2.2.1" + loose-envify "^1.2.0" + query-string "^4.2.2" + warning "^3.0.0" + hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" @@ -6700,7 +6783,7 @@ interpret@^1.0.0: resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== -invariant@^2.2.2, invariant@^2.2.4: +invariant@^2.2.1, invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== @@ -7041,7 +7124,7 @@ is-stream-ended@^0.1.4: resolved "https://registry.yarnpkg.com/is-stream-ended/-/is-stream-ended-0.1.4.tgz#f50224e95e06bce0e356d440a4827cd35b267eda" integrity sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw== -is-stream@^1.0.0, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -7143,6 +7226,14 @@ isobject@^3.0.0, isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= +isomorphic-fetch@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" + integrity sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk= + dependencies: + node-fetch "^1.0.1" + whatwg-fetch ">=0.10.0" + isstream@0.1.2, isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -8362,7 +8453,7 @@ longest@^1.0.1: resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" integrity sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc= -loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3.1, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -9083,6 +9174,14 @@ node-fetch-npm@^2.0.2: json-parse-better-errors "^1.0.0" safe-buffer "^5.1.1" +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + node-fetch@^2.2.0, node-fetch@^2.3.0, node-fetch@^2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" @@ -10484,6 +10583,13 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" +promise@^7.1.1: + version "7.3.1" + resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" + integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== + dependencies: + asap "~2.0.3" + prompts@^2.0.1: version "2.3.2" resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068" @@ -10499,7 +10605,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.6.2: +prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -10650,6 +10756,14 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +query-string@^4.2.2: + version "4.3.4" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-4.3.4.tgz#bbb693b9ca915c232515b228b1a02b609043dbeb" + integrity sha1-u7aTucqRXCMlFbIosaArYJBD2+s= + dependencies: + object-assign "^4.1.0" + strict-uri-encode "^1.0.0" + querystring-es3@^0.2.0, querystring-es3@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" @@ -10729,11 +10843,25 @@ react-dom@^16.0.0: prop-types "^15.6.2" scheduler "^0.19.1" -react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: +react-is@^16.12.0, react-is@^16.13.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +"react-router-3@npm:react-router@^3.2.0": + version "3.2.6" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.2.6.tgz#cad202796a7bba3efc2100da453b3379c9d4aeb4" + integrity sha512-nlxtQE8B22hb/JxdaslI1tfZacxFU8x8BJryXOnR2RxB4vc01zuHYAHAIgmBkdk1kzXaA25hZxK6KAH/+CXArw== + dependencies: + create-react-class "^15.5.1" + history "^3.0.0" + hoist-non-react-statics "^3.3.2" + invariant "^2.2.1" + loose-envify "^1.2.0" + prop-types "^15.7.2" + react-is "^16.13.0" + warning "^3.0.0" + react-test-renderer@^16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.13.1.tgz#de25ea358d9012606de51e012d9742e7f0deabc1" @@ -11580,7 +11708,7 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= @@ -12065,6 +12193,11 @@ streamroller@^1.0.6: fs-extra "^7.0.1" lodash "^4.17.14" +strict-uri-encode@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -12869,6 +13002,11 @@ typescript@3.4.5: resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== +ua-parser-js@^0.7.18: + version "0.7.21" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.21.tgz#853cf9ce93f642f67174273cc34565ae6f308777" + integrity sha512-+O8/qh/Qj8CgC6eYBVBykMrNtp5Gebn4dlGD/kKXVkJNDwyrAwSIqwz8CDf+tsAIWVycKcku6gIXJ0qwx/ZXaQ== + uglify-js@^2.6: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" @@ -13244,6 +13382,13 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" +warning@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/warning/-/warning-3.0.0.tgz#32e5377cb572de4ab04753bdf8821c01ed605b7c" + integrity sha1-MuU3fLVy3kqwR1O9+IIcAe1gW3w= + dependencies: + loose-envify "^1.0.0" + watchpack-chokidar2@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz#9948a1866cbbd6cb824dea13a7ed691f6c8ddff0" @@ -13347,6 +13492,11 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" +whatwg-fetch@>=0.10.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.2.0.tgz#8e134f701f0a4ab5fda82626f113e2b647fd16dc" + integrity sha512-SdGPoQMMnzVYThUbSrEvqTlkvC1Ux27NehaJ/GUHBfNrh5Mjg+1/uRyFMwVnxO2MrikMWvWAqUGgQOfVU4hT7w== + whatwg-mimetype@^2.1.0, whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"