1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-10-22 03:12:42 +02:00

remove matomo and route to segment (#9646)

This commit is contained in:
Brad Decker 2020-10-26 14:05:57 -05:00 committed by GitHub
parent 379950d53f
commit 7d50357684
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 123 additions and 362 deletions

View File

@ -1,14 +0,0 @@
import { getBackgroundMetaMetricState } from '../../../ui/app/selectors'
import { sendMetaMetricsEvent } from '../../../ui/app/helpers/utils/metametrics.util'
export default function backgroundMetaMetricsEvent (metaMaskState, version, eventData) {
const stateEventData = getBackgroundMetaMetricState({ metamask: metaMaskState })
if (stateEventData.participateInMetaMetrics) {
sendMetaMetricsEvent({
...stateEventData,
...eventData,
version,
currentPath: '/background',
})
}
}

View File

@ -25,6 +25,7 @@ import {
PhishingController,
} from '@metamask/controllers'
import { getTrackMetaMetricsEvent } from '../../shared/modules/metametrics'
import { getBackgroundMetaMetricState } from '../../ui/app/selectors'
import ComposableObservableStore from './lib/ComposableObservableStore'
import AccountTracker from './lib/account-tracker'
import createLoggerMiddleware from './lib/createLoggerMiddleware'
@ -56,8 +57,6 @@ import getRestrictedMethods from './controllers/permissions/restrictedMethods'
import nodeify from './lib/nodeify'
import accountImporter from './account-import-strategies'
import seedPhraseVerifier from './lib/seed-phrase-verifier'
import backgroundMetaMetricsEvent from './lib/background-metametrics'
import { ENVIRONMENT_TYPE_BACKGROUND } from './lib/enums'
export default class MetamaskController extends EventEmitter {
@ -1893,19 +1892,18 @@ export default class MetamaskController extends EventEmitter {
}
const metamaskState = await this.getState()
const version = this.platform.getVersion()
backgroundMetaMetricsEvent(
metamaskState,
version,
{
customVariables,
eventOpts: {
action,
category: 'Background',
name,
},
const additionalProperties = getBackgroundMetaMetricState(metamaskState)
this.trackMetaMetricsEvent({
event: name,
category: 'Background',
matomoEvent: true,
properties: {
action,
...additionalProperties,
...customVariables,
},
)
})
}
/**

View File

@ -18,6 +18,7 @@ const { makeStringTransform } = require('browserify-transform-tools')
const conf = require('rc')('metamask', {
INFURA_PROJECT_ID: process.env.INFURA_PROJECT_ID,
SEGMENT_WRITE_KEY: process.env.SEGMENT_WRITE_KEY,
SEGMENT_LEGACY_WRITE_KEY: process.env.SEGMENT_LEGACY_WRITE_KEY,
})
const packageJSON = require('../../package.json')
@ -324,6 +325,7 @@ function createScriptTasks ({ browserPlatforms, livereload }) {
// inflating event volume.
const SEGMENT_PROD_WRITE_KEY = opts.testing ? undefined : process.env.SEGMENT_PROD_WRITE_KEY
const SEGMENT_DEV_WRITE_KEY = opts.testing ? undefined : conf.SEGMENT_WRITE_KEY
const SEGMENT_LEGACY_WRITE_KEY = opts.testing ? undefined : conf.SEGMENT_LEGACY_WRITE_KEY
// Inject variables into bundle
bundler.transform(envify({
@ -343,6 +345,7 @@ function createScriptTasks ({ browserPlatforms, livereload }) {
: conf.INFURA_PROJECT_ID
),
SEGMENT_WRITE_KEY: environment === 'production' ? SEGMENT_PROD_WRITE_KEY : SEGMENT_DEV_WRITE_KEY,
SEGMENT_LEGACY_WRITE_KEY: environment === 'production' ? process.env.SEGMENT_LEGACY_WRITE_KEY : SEGMENT_LEGACY_WRITE_KEY,
}), {
global: true,
})

View File

@ -1,72 +0,0 @@
## Creating Metrics Events
The `metricsEvent` method is made available to all components via context. This is done in `metamask-extension/ui/app/helpers/higher-order-components/metametrics/metametrics.provider.js`. As such, it can be called in all components by first adding it to the context proptypes:
```
static contextTypes = {
t: PropTypes.func,
metricsEvent: PropTypes.func,
}
```
and then accessing it on `this.context`.
Below is an example of a metrics event call:
```
this.context.metricsEvent({
eventOpts: {
category: 'Navigation',
action: 'Main Menu',
name: 'Switched Account',
},
})
```
### Base Schema
Every `metricsEvent` call is passed an object that must have an `eventOpts` property. This property is an object that itself must have three properties:
- category: categorizes events according to the schema we have set up in our matomo.org instance
- action: usually describes the page on which the event takes place, or sometimes a significant subsections of a page
- name: a very specific descriptor of the event
### Implicit properties
All metrics events send the following data when called:
- network
- environmentType
- activeCurrency
- accountType
- numberOfTokens
- numberOfAccounts
- version
These are added to the metrics event via the metametrics provider.
### Custom Variables
Metrics events can include custom variables. These are included within the `customVariables` property that is a first-level property within first param passed to `metricsEvent`.
For example:
```
this.context.metricsEvent({
eventOpts: {
category: 'Settings',
action: 'Custom RPC',
name: 'Error',
},
customVariables: {
networkId: newRpc,
chainId,
},
})
```
Custom variables can have custom property names and values can be strings or numbers.
**To include a custom variable, there are a set of necessary steps you must take.**
1. First you must declare a constant equal to the desired name of the custom variable property in `metamask-extension/ui/app/helpers/utils/metametrics.util.js` under `//Custom Variable Declarations`
1. Then you must add that name to the `customVariableNameIdMap` declaration
1. The id must be between 1 and 5
1. There can be no more than 5 custom variables assigned ids on a given url

View File

@ -198,6 +198,7 @@
"@storybook/storybook-deployer": "^2.8.6",
"@testing-library/react": "^10.4.8",
"@testing-library/react-hooks": "^3.2.1",
"@types/react": "^16.9.53",
"addons-linter": "1.14.0",
"babel-loader": "^8.0.6",
"babelify": "^10.0.0",

View File

@ -57,6 +57,10 @@ export const segment = process.env.SEGMENT_WRITE_KEY
? new Analytics(process.env.SEGMENT_WRITE_KEY, { flushAt })
: segmentNoop
export const segmentLegacy = process.env.SEGMENT_LEGACY_WRITE_KEY
? new Analytics(process.env.SEGMENT_LEGACY_WRITE_KEY, { flushAt })
: segmentNoop
/**
* We attach context to every meta metrics event that help to qualify our analytics.
* This type has all optional values because it represents a returned object from a
@ -111,6 +115,12 @@ export const segment = process.env.SEGMENT_WRITE_KEY
* @property {string} [currency] - ISO 4127 format currency for events with revenue, defaults to US dollars
* @property {number} [value] - Abstract "value" that this event has for MetaMask.
* @property {boolean} [excludeMetaMetricsId] - whether to exclude the user's metametrics id for anonymity
* @property {string} [metaMetricsId] - an override for the metaMetricsId in the event one is created as part
* of an asynchronous workflow, such as awaiting the result of the metametrics opt-in function that generates the
* user's metametrics id.
* @property {boolean} [matomoEvent] - is this event a holdover from matomo that needs further migration?
* when true, sends the data to a special segment source that marks the event data as not conforming to our
* ideal schema
* @property {MetaMetricsDynamicContext} [eventContext] - additional context to attach to event
*/
@ -137,7 +147,9 @@ export function getTrackMetaMetricsEvent (
revenue,
currency,
value,
metaMetricsId: metaMetricsIdOverride,
excludeMetaMetricsId: excludeId,
matomoEvent = false,
eventContext = {},
}) {
if (!event || !category) {
@ -199,8 +211,20 @@ export function getTrackMetaMetricsEvent (
context,
}
// If we are tracking sensitive data we will always use the anonymousId property
// as well as our METAMETRICS_ANONYMOUS_ID. This prevents us from associating potentially
// identifiable information with a specific id. During the opt in flow we will track all
// events, but do so with the anonymous id. The one exception to that rule is after the
// user opts in to MetaMetrics. When that happens we receive back the user's new MetaMetrics
// id before it is fully persisted to state. To avoid a race condition we explicitly pass the
// new id to the track method. In that case we will track the opt in event to the user's id.
// In all other cases we use the metaMetricsId from state.
if (excludeMetaMetricsId) {
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID
} else if (isOptIn && metaMetricsIdOverride) {
trackOptions.userId = metaMetricsIdOverride
} else if (isOptIn) {
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID
} else {
trackOptions.userId = metaMetricsId
}
@ -210,12 +234,18 @@ export function getTrackMetaMetricsEvent (
// If flushAt is greater than one the callback won't be triggered until after a number
// of events have been queued equal to the flushAt value OR flushInterval passes. The
// default flushInterval is ten seconds
segment.track(trackOptions, (err) => {
const callback = (err) => {
if (err) {
return reject(err)
}
return resolve()
})
}
if (matomoEvent === true) {
segmentLegacy.track(trackOptions, callback)
} else {
segment.track(trackOptions, callback)
}
})
}
}

View File

@ -1,4 +1,4 @@
import React, { Component, createContext, useEffect, useCallback, useState } from 'react'
import React, { Component, createContext, useEffect, useCallback, useState, useMemo } from 'react'
import { useSelector } from 'react-redux'
import PropTypes from 'prop-types'
import { useHistory } from 'react-router-dom'
@ -9,16 +9,15 @@ import {
getAccountType,
getNumberOfAccounts,
getNumberOfTokens,
getCurrentChainId,
} from '../selectors/selectors'
import { getSendToken } from '../selectors/send'
import {
txDataSelector,
} from '../selectors/confirm-transaction'
import { getEnvironmentType } from '../../../app/scripts/lib/util'
import {
sendMetaMetricsEvent,
} from '../helpers/utils/metametrics.util'
import { sendCountIsTrackable } from '../../../shared/modules/metametrics'
import { getTrackMetaMetricsEvent } from '../../../shared/modules/metametrics'
import { getCurrentLocale } from '../ducks/metamask/metamask'
export const MetaMetricsContext = createContext(() => {
captureException(
@ -30,6 +29,8 @@ export function MetaMetricsProvider ({ children }) {
const txData = useSelector(txDataSelector) || {}
const network = useSelector(getCurrentNetworkId)
const environmentType = getEnvironmentType()
const chainId = useSelector(getCurrentChainId)
const locale = useSelector(getCurrentLocale)
const activeCurrency = useSelector(getSendToken)?.symbol
const accountType = useSelector(getAccountType)
const confirmTransactionOrigin = txData.origin
@ -44,7 +45,7 @@ export function MetaMetricsProvider ({ children }) {
previousPath: '',
}))
const { previousPath, currentPath } = state
const { currentPath } = state
useEffect(() => {
const unlisten = history.listen(() => setState((prevState) => ({
@ -55,45 +56,63 @@ export function MetaMetricsProvider ({ children }) {
return unlisten
}, [history])
/**
* track a metametrics event
*
* @param {import('../../../shared/modules/metametrics').MetaMetricsEventPayload} - payload for event
* @returns undefined
*/
const trackEvent = useMemo(() => {
const referrer = confirmTransactionOrigin ? { url: confirmTransactionOrigin } : undefined
const page = {
path: currentPath,
}
return getTrackMetaMetricsEvent(global.platform.getVersion(), () => ({
context: {
referrer,
page,
},
environmentType,
locale: locale.replace('_', '-'),
network,
chainId,
participateInMetaMetrics,
metaMetricsId,
metaMetricsSendCount,
}))
}, [network, chainId, locale, environmentType, participateInMetaMetrics, currentPath, confirmTransactionOrigin, metaMetricsId, metaMetricsSendCount])
const metricsEvent = useCallback((config = {}, overrides = {}) => {
const { eventOpts = {} } = config
const { name = '' } = eventOpts
const { currentPath: overrideCurrentPath = '' } = overrides
const isSendFlow = Boolean(name.match(/^send|^confirm/u) || overrideCurrentPath.match(/send|confirm/u))
if (participateInMetaMetrics || config.isOptIn) {
return sendMetaMetricsEvent({
network,
environmentType,
activeCurrency,
accountType,
confirmTransactionOrigin,
metaMetricsId,
numberOfTokens,
numberOfAccounts,
version: global.platform.getVersion(),
...config,
previousPath,
currentPath,
excludeMetaMetricsId: isSendFlow && !sendCountIsTrackable(metaMetricsSendCount + 1),
...overrides,
})
}
return undefined
return trackEvent({
event: eventOpts.name,
category: eventOpts.category,
isOptIn: config.isOptIn,
excludeMetaMetricsId: eventOpts.excludeMetaMetricsId ?? overrides.excludeMetaMetricsId ?? false,
metaMetricsId: config.metaMetricsId,
matomoEvent: true,
properties: {
action: eventOpts.action,
number_of_tokens: numberOfTokens,
number_of_accounts: numberOfAccounts,
active_currency: activeCurrency,
account_type: accountType,
is_new_visit: config.is_new_visit,
// the properties coming from this key will not match our standards for
// snake_case on properties, and they may be redundant and/or not in the
// proper location (origin not as a referrer, for example). This is a temporary
// solution to not lose data, and the entire event system will be reworked in
// forthcoming PRs to deprecate the old Matomo events in favor of the new schema.
...config.customVariables,
},
})
}, [
network,
environmentType,
activeCurrency,
accountType,
confirmTransactionOrigin,
participateInMetaMetrics,
previousPath,
metaMetricsId,
activeCurrency,
numberOfTokens,
numberOfAccounts,
currentPath,
metaMetricsSendCount,
trackEvent,
])
return (

View File

@ -1,216 +0,0 @@
/* eslint camelcase: 0 */
import ethUtil from 'ethereumjs-util'
const inDevelopment = process.env.METAMASK_DEBUG || process.env.IN_TEST
let projectId = process.env.METAMETRICS_PROJECT_ID
if (!projectId) {
projectId = inDevelopment ? 1 : 2
}
const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.php'
const METAMETRICS_REQUIRED_PARAMS = `?idsite=${projectId}&rec=1&apiv=1`
const METAMETRICS_BASE_FULL = METAMETRICS_BASE_URL + METAMETRICS_REQUIRED_PARAMS
const METAMETRICS_TRACKING_URL = inDevelopment
? 'http://www.metamask.io/metametrics'
: 'http://www.metamask.io/metametrics-prod'
/** ***************Custom variables*************** **/
// Custom variable declarations
const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange'
const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange'
const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType'
const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown'
const METAMETRICS_REQUEST_ORIGIN = 'origin'
const METAMETRICS_CUSTOM_FROM_NETWORK = 'fromNetwork'
const METAMETRICS_CUSTOM_TO_NETWORK = 'toNetwork'
const METAMETRICS_CUSTOM_ERROR_FIELD = 'errorField'
const METAMETRICS_CUSTOM_ERROR_MESSAGE = 'errorMessage'
const METAMETRICS_CUSTOM_RPC_NETWORK_ID = 'networkId'
const METAMETRICS_CUSTOM_RPC_CHAIN_ID = 'chainId'
const METAMETRICS_CUSTOM_GAS_CHANGED = 'gasChanged'
const METAMETRICS_CUSTOM_ASSET_SELECTED = 'assetSelected'
const customVariableNameIdMap = {
[METAMETRICS_CUSTOM_FUNCTION_TYPE]: 1,
[METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 2,
[METAMETRICS_REQUEST_ORIGIN]: 3,
[METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4,
[METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5,
[METAMETRICS_CUSTOM_FROM_NETWORK]: 1,
[METAMETRICS_CUSTOM_TO_NETWORK]: 2,
[METAMETRICS_CUSTOM_RPC_NETWORK_ID]: 1,
[METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 2,
[METAMETRICS_CUSTOM_ERROR_FIELD]: 3,
[METAMETRICS_CUSTOM_ERROR_MESSAGE]: 4,
[METAMETRICS_CUSTOM_GAS_CHANGED]: 1,
[METAMETRICS_CUSTOM_ASSET_SELECTED]: 2,
}
/** ********************************************************** **/
const METAMETRICS_CUSTOM_NETWORK = 'network'
const METAMETRICS_CUSTOM_ENVIRONMENT_TYPE = 'environmentType'
const METAMETRICS_CUSTOM_ACTIVE_CURRENCY = 'activeCurrency'
const METAMETRICS_CUSTOM_ACCOUNT_TYPE = 'accountType'
const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts'
const METAMETRICS_CUSTOM_NUMBER_OF_TOKENS = 'numberOfTokens'
const METAMETRICS_CUSTOM_VERSION = 'version'
const customDimensionsNameIdMap = {
[METAMETRICS_CUSTOM_NETWORK]: 5,
[METAMETRICS_CUSTOM_ENVIRONMENT_TYPE]: 6,
[METAMETRICS_CUSTOM_ACTIVE_CURRENCY]: 7,
[METAMETRICS_CUSTOM_ACCOUNT_TYPE]: 8,
[METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS]: 9,
[METAMETRICS_CUSTOM_NUMBER_OF_TOKENS]: 10,
[METAMETRICS_CUSTOM_VERSION]: 11,
}
function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) {
const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'metamask'
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(`${METAMETRICS_TRACKING_URL}${previousPath}`)}`
}
// composes query params of the form &dimension[0-999]=[value]
function composeCustomDimensionParamAddition (customDimensions) {
const customDimensionParamStrings = Object.keys(customDimensions).reduce((acc, name) => {
return [...acc, `dimension${customDimensionsNameIdMap[name]}=${customDimensions[name]}`]
}, [])
return `&${customDimensionParamStrings.join('&')}`
}
// composes query params in form: &cvar={[id]:[[name],[value]]}
// Example: &cvar={"1":["OS","iphone 5.0"],"2":["Matomo Mobile Version","1.6.2"],"3":["Locale","en::en"],"4":["Num Accounts","2"]}
function composeCustomVarParamAddition (customVariables) {
const customVariableIdValuePairs = Object.keys(customVariables).reduce((acc, name) => {
return {
[customVariableNameIdMap[name]]: [name, customVariables[name]],
...acc,
}
}, {})
return `&cvar=${encodeURIComponent(JSON.stringify(customVariableIdValuePairs))}`
}
function composeParamAddition (paramValue, paramName) {
return paramValue !== 0 && !paramValue
? ''
: `&${paramName}=${paramValue}`
}
/**
* @name composeUrl
* @param {Object} config - configuration object for composing the metametrics url
* @property {object} config.eventOpts Object containing event category, action and name descriptors
* @property {object} config.customVariables Object containing custom properties with values relevant to a specific event
* @property {object} config.pageOpts Objects containing information about a page/route the event is dispatched from
* @property {number} config.network The selected network of the user when the event occurs
* @property {string} config.environmentType The "environment" the user is using the app from: 'popup', 'notification' or 'fullscreen'
* @property {string} config.activeCurrency The current the user has select as their primary currency at the time of the event
* @property {string} config.accountType The account type being used at the time of the event: 'hardware', 'imported' or 'default'
* @property {number} config.numberOfTokens The number of tokens that the user has added at the time of the event
* @property {number} config.numberOfAccounts The number of accounts the user has added at the time of the event
* @property {string} config.version The current version of the MetaMask extension
* @property {string} config.previousPath The pathname of the URL the user was on prior to the URL they are on at the time of the event
* @property {string} config.currentPath The pathname of the URL the user is on at the time of the event
* @property {string} config.metaMetricsId A random id assigned to a user at the time of opting in to metametrics. A hexadecimal number
* @property {string} config.confirmTransactionOrigin The origin on a transaction
* @property {boolean} config.excludeMetaMetricsId Whether or not the tracked event data should be associated with a metametrics id
* @property {boolean} config.isNewVisit Whether or not the event should be tracked as a new visit/user sessions
* @returns {string} - Returns a url to be passed to fetch to make the appropriate request to matomo.
* Example: https://chromeextensionmm.innocraft.cloud/piwik.php?idsite=1&rec=1&apiv=1&e_c=Navigation&e_a=Home&e_n=Clicked%20Send:%20Eth&urlref=http%3A%2F%2Fwww.metamask.io%2Fmetametrics%2Fhome.html%23send&dimension5=3&dimension6=fullscreen&dimension7=ETH&dimension8=default&dimension9=0&dimension10=3&url=http%3A%2F%2Fwww.metamask.io%2Fmetametrics%2Fhome.html%23&_id=49c10aff19795e9a&rand=7906028754863992&pv_id=53acad&uid=49c1
*/
function composeUrl (config) {
const {
eventOpts = {},
customVariables = '',
pageOpts = '',
network,
environmentType,
activeCurrency,
accountType,
numberOfTokens,
numberOfAccounts,
version,
previousPath = '',
currentPath,
metaMetricsId,
confirmTransactionOrigin,
excludeMetaMetricsId,
isNewVisit,
} = config
const base = METAMETRICS_BASE_FULL
const e_c = composeParamAddition(eventOpts.category, 'e_c')
const e_a = composeParamAddition(eventOpts.action, 'e_a')
const e_n = composeParamAddition(eventOpts.name, 'e_n')
const new_visit = isNewVisit ? `&new_visit=1` : ''
const cvar = (customVariables && composeCustomVarParamAddition(customVariables)) || ''
const action_name = ''
const urlref = previousPath && composeUrlRefParamAddition(previousPath, confirmTransactionOrigin)
const dimensions = pageOpts.hideDimensions
? ''
: (
composeCustomDimensionParamAddition({
network,
environmentType,
activeCurrency,
accountType,
version,
numberOfTokens: (customVariables && customVariables.numberOfTokens) || numberOfTokens,
numberOfAccounts: (customVariables && customVariables.numberOfAccounts) || numberOfAccounts,
})
)
const url = currentPath ? `&url=${encodeURIComponent(`${METAMETRICS_TRACKING_URL}${currentPath}`)}` : ''
const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : ''
const rand = `&rand=${String(Math.random()).slice(2)}`
const pv_id = currentPath ? `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(currentPath)).slice(2, 8)}` : ''
let uid = ''
if (excludeMetaMetricsId) {
uid = '&uid=0000000000000000'
} else if (metaMetricsId) {
uid = `&uid=${metaMetricsId.slice(2, 18)}`
}
return [base, e_c, e_a, e_n, cvar, action_name, urlref, dimensions, url, _id, rand, pv_id, uid, new_visit].join('')
}
export function sendMetaMetricsEvent (config) {
return window.fetch(composeUrl(config), {
'headers': {},
'method': 'GET',
})
}
export function verifyUserPermission (config, props) {
const {
eventOpts = {},
} = config
const { userPermissionPreferences } = props
const {
allowAll,
allowNone,
allowSendMetrics,
} = userPermissionPreferences
if (allowNone) {
return false
} else if (allowAll) {
return true
} else if (allowSendMetrics && eventOpts.name === 'send') {
return true
}
return false
}

View File

@ -101,12 +101,6 @@ export default class Routes extends Component {
this.props.history.listen((locationObj, action) => {
if (action === 'PUSH') {
pageChanged(locationObj.pathname)
this.context.metricsEvent({}, {
currentPath: locationObj.pathname,
pageOpts: {
hideDimensions: true,
},
})
}
})
}

View File

@ -3063,6 +3063,11 @@
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.1.tgz#be148756d5480a84cde100324c03a86ae5739fb5"
integrity sha512-2zs+O+UkDsJ1Vcp667pd3f8xearMdopz/z54i99wtRDI5KLmngk7vlrYZD0ZjKHaROR03EznlBbVY9PfAEyJIQ==
"@types/prop-types@*":
version "15.7.3"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7"
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/q@^1.5.1":
version "1.5.2"
resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8"
@ -3118,6 +3123,14 @@
dependencies:
csstype "^2.2.0"
"@types/react@^16.9.53":
version "16.9.53"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.53.tgz#40cd4f8b8d6b9528aedd1fff8fcffe7a112a3d23"
integrity sha512-4nW60Sd4L7+WMXH1D6jCdVftuW7j4Za6zdp6tJ33Rqv0nk1ZAmQKML9ZLD4H0dehA3FZxXR/GM8gXplf82oNGw==
dependencies:
"@types/prop-types" "*"
csstype "^3.0.2"
"@types/redux@^3.6.0":
version "3.6.0"
resolved "https://registry.yarnpkg.com/@types/redux/-/redux-3.6.0.tgz#f1ebe1e5411518072e4fdfca5c76e16e74c1399a"
@ -8145,6 +8158,11 @@ csstype@^2.5.7:
resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.5.tgz#1cd1dff742ebf4d7c991470ae71e12bb6751e034"
integrity sha512-JsTaiksRsel5n7XwqPAfB0l3TFKdpjW/kgAELf9vrb5adGA7UCPLajKK5s3nFrcFm3Rkyp/Qkgl73ENc1UY3cA==
csstype@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8"
integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag==
currency-formatter@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/currency-formatter/-/currency-formatter-1.4.2.tgz#9da20b3706f7a42daf73b356b09a2a2b76ff3870"