Metametrics (#6171)
* Add metametrics provider and util. * Add backend api and state for participating in metametrics. * Add frontend action for participating in metametrics. * Add metametrics opt-in screen. * Add metametrics events to first time flow. * Add metametrics events for route changes * Add metametrics events for send and confirm screens * Add metametrics events to dropdowns, transactions, log in and out, settings, sig requests and main screen * Ensures each log in is measured as a new visit by metametrics. * Ensure metametrics is called with an empty string for dimensions params if specified * Adds opt in metametrics modal after unlock for existing users * Adds settings page toggle for opting in and out of MetaMetrics * Switch metametrics dimensions to page level scope * Lint, test and translation fixes for metametrics. * Update design for metametrics opt-in screen * Complete responsive styling of metametrics-opt-in modal * Use new chart image on metrics opt in screens * Incorporate the metametrics opt-in screen into the new onboarding flow * Update e2e tests to accomodate metametrics changes * Mock out metametrics network requests in integration tests * Fix tx-list integration test to support metametrics provider. * Send number of tokens and accounts data with every metametrics event. * Update metametrics event descriptor schema and add new events. * Fix import tos bug and send gas button bug due to metametrics changes. * Various small fixes on the metametrics branch. * Add origin custom variable type to metametrics.util * Fix names of onboarding complete actions (metametrics). * Fix names of Metrics Options actions (metametrics). * Clean up code related to metametrics. * Fix bad merge conflict resolution and improve promise handling in sendMetaMetrics event and confrim tx base * Don't send a second metrics event if user has gone back during first time flow. * Collect metametrics on going back from onboarding create/import. * Add missing custom variable constants for metametrics * Fix metametrics provider * Make height of opt-in modal responsive. * Adjust text content for opt-in modal. * Update metametrics event names and clean up code in opt-in-modal * Put phishing warning step next to last in onboarding flow * Link terms of service on create and import screens of first time flow * Add subtext to options on the onboarding select action screen. * Fix styling of bullet points on end of onboarding screen. * Combine phishing warning and congratulations screens. * Fix placement of users if unlocking after an incomplete onboarding import flow. * Fix capitalization in opt-in screen * Fix last onboarding screen translations * Add link to 'Learn More' on the last screen of onboarding * Code clean up: metametrics branch * Update e2e tests for phishing warning step removal * e2e tests passing on metametrics branch * Different tracking urls for metametrics on development and prodfeature/default_network_editable
parent
1765864e40
commit
c757366355
After Width: | Height: | Size: 2.5 KiB |
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@ |
||||
export { default } from './metametrics-opt-in-modal.container' |
@ -0,0 +1,30 @@ |
||||
.metametrics-opt-in-modal { |
||||
.metametrics-opt-in__main { |
||||
justify-content: center; |
||||
margin-left: 3%; |
||||
margin-right: 0%; |
||||
max-height: 75vh; |
||||
|
||||
@media screen and (max-width: 575px) { |
||||
max-height: 100vh; |
||||
} |
||||
} |
||||
|
||||
|
||||
.metametrics-opt-in__title { |
||||
font-size: 38px; |
||||
} |
||||
|
||||
.metametrics-opt-in__content { |
||||
padding-right: 6px; |
||||
} |
||||
|
||||
.metametrics-opt-in__footer { |
||||
@media screen and (max-width: 575px) { |
||||
margin-top: 10px; |
||||
justify-content: center; |
||||
margin-left: 2%; |
||||
max-height: 520px; |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,135 @@ |
||||
import React, { Component } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import PageContainerFooter from '../../page-container/page-container-footer' |
||||
|
||||
export default class MetaMetricsOptInModal extends Component { |
||||
static propTypes = { |
||||
setParticipateInMetaMetrics: PropTypes.func, |
||||
hideModal: PropTypes.func, |
||||
} |
||||
|
||||
static contextTypes = { |
||||
metricsEvent: PropTypes.func, |
||||
} |
||||
|
||||
render () { |
||||
const { metricsEvent } = this.context |
||||
const { setParticipateInMetaMetrics, hideModal } = this.props |
||||
|
||||
return ( |
||||
<div className="metametrics-opt-in metametrics-opt-in-modal"> |
||||
<div className="metametrics-opt-in__main"> |
||||
<div className="metametrics-opt-in__content"> |
||||
<div className="app-header__logo-container"> |
||||
<img |
||||
className="app-header__metafox-logo app-header__metafox-logo--horizontal" |
||||
src="/images/logo/metamask-logo-horizontal.svg" |
||||
height={30} |
||||
/> |
||||
<img |
||||
className="app-header__metafox-logo app-header__metafox-logo--icon" |
||||
src="/images/logo/metamask-fox.svg" |
||||
height={42} |
||||
width={42} |
||||
/> |
||||
</div> |
||||
<div className="metametrics-opt-in__body-graphic"> |
||||
<img src="images/metrics-chart.svg" /> |
||||
</div> |
||||
<div className="metametrics-opt-in__title">Help Us Improve MetaMask</div> |
||||
<div className="metametrics-opt-in__body"> |
||||
<div className="metametrics-opt-in__description"> |
||||
MetaMask would like to gather usage data to better understand how our users interact with the extension. This data |
||||
will be used to continually improve the usability and user experience of our product and the etheruem ecosystem. |
||||
</div> |
||||
<div className="metametrics-opt-in__description"> |
||||
MetaMask will.. |
||||
</div> |
||||
|
||||
<div className="metametrics-opt-in__committments"> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-check" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
Always allow you to opt-out via Settings |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-check" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
Send anonymized click & pageview events |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-check" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
Maintain a public aggregate dashboard to educate the community |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row metametrics-opt-in__break-row"> |
||||
<i className="fa fa-times" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
<span className="metametrics-opt-in__bold">Never</span> collect keys, addresses, transactions, balances, hashes, or any personal information |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-times" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
<span className="metametrics-opt-in__bold">Never</span> collect your full IP address |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-times" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
<span className="metametrics-opt-in__bold">Never</span> sell data for profit. Ever! |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__bottom-text"> |
||||
This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our Privacy Policy here. |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__footer"> |
||||
<PageContainerFooter |
||||
onCancel={() => { |
||||
setParticipateInMetaMetrics(false) |
||||
.then(() => { |
||||
metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Onboarding', |
||||
action: 'Metrics Option', |
||||
name: 'Metrics Opt Out', |
||||
}, |
||||
isOptIn: true, |
||||
}, { |
||||
excludeMetaMetricsId: true, |
||||
}) |
||||
hideModal() |
||||
}) |
||||
}} |
||||
cancelText={'No Thanks'} |
||||
hideCancel={false} |
||||
onSubmit={() => { |
||||
setParticipateInMetaMetrics(true) |
||||
.then(() => { |
||||
metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Onboarding', |
||||
action: 'Metrics Option', |
||||
name: 'Metrics Opt In', |
||||
}, |
||||
isOptIn: true, |
||||
}) |
||||
hideModal() |
||||
}) |
||||
}} |
||||
submitText={'I agree'} |
||||
submitButtonType={'confirm'} |
||||
disabled={false} |
||||
/> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,24 @@ |
||||
import { connect } from 'react-redux' |
||||
import { compose } from 'recompose' |
||||
import MetaMetricsOptInModal from './metametrics-opt-in-modal.component' |
||||
import withModalProps from '../../../higher-order-components/with-modal-props' |
||||
import { setParticipateInMetaMetrics } from '../../../actions' |
||||
|
||||
const mapStateToProps = (state, ownProps) => { |
||||
const { unapprovedTxCount } = ownProps |
||||
|
||||
return { |
||||
unapprovedTxCount, |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), |
||||
} |
||||
} |
||||
|
||||
export default compose( |
||||
withModalProps, |
||||
connect(mapStateToProps, mapDispatchToProps), |
||||
)(MetaMetricsOptInModal) |
@ -0,0 +1,26 @@ |
||||
import { |
||||
INITIALIZE_CREATE_PASSWORD_ROUTE, |
||||
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE, |
||||
DEFAULT_ROUTE, |
||||
} from '../../../routes' |
||||
|
||||
const selectors = { |
||||
getFirstTimeFlowTypeRoute, |
||||
} |
||||
|
||||
module.exports = selectors |
||||
|
||||
function getFirstTimeFlowTypeRoute (state) { |
||||
const { firstTimeFlowType } = state.metamask |
||||
|
||||
let nextRoute |
||||
if (firstTimeFlowType === 'create') { |
||||
nextRoute = INITIALIZE_CREATE_PASSWORD_ROUTE |
||||
} else if (firstTimeFlowType === 'import') { |
||||
nextRoute = INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE |
||||
} else { |
||||
nextRoute = DEFAULT_ROUTE |
||||
} |
||||
|
||||
return nextRoute |
||||
} |
@ -0,0 +1 @@ |
||||
export { default } from './metametrics-opt-in.container' |
@ -0,0 +1,136 @@ |
||||
.metametrics-opt-in { |
||||
position: relative; |
||||
width: 100%; |
||||
|
||||
a { |
||||
color: #2f9ae0bf; |
||||
} |
||||
|
||||
&__main { |
||||
display: flex; |
||||
flex-direction: column; |
||||
margin-left: 26.26%; |
||||
margin-right: 28%; |
||||
color: black; |
||||
|
||||
@media screen and (max-width: 575px) { |
||||
justify-content: center; |
||||
margin-left: 2%; |
||||
margin-right: 0%; |
||||
} |
||||
|
||||
.app-header__logo-container { |
||||
margin-top: 3%; |
||||
} |
||||
} |
||||
|
||||
&__title { |
||||
position: relative; |
||||
margin-top: 20px; |
||||
|
||||
font-family: Roboto; |
||||
font-style: normal; |
||||
font-weight: normal; |
||||
line-height: normal; |
||||
font-size: 42px; |
||||
} |
||||
|
||||
&__body-graphic { |
||||
margin-top: 25px; |
||||
|
||||
.fa-bar-chart { |
||||
color: #C4C4C4; |
||||
} |
||||
} |
||||
|
||||
&__description { |
||||
font-family: Roboto; |
||||
font-style: normal; |
||||
font-weight: normal; |
||||
line-height: 21px; |
||||
font-size: 16px; |
||||
margin-top: 12px; |
||||
} |
||||
|
||||
&__committments { |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
&__content { |
||||
overflow-y: scroll; |
||||
flex: 1; |
||||
} |
||||
|
||||
&__row { |
||||
display: flex; |
||||
margin-top: 8px; |
||||
|
||||
.fa-check { |
||||
margin-right: 12px; |
||||
color: #1ACC56; |
||||
} |
||||
|
||||
.fa-times { |
||||
margin-right: 12px; |
||||
color: #D0021B; |
||||
} |
||||
} |
||||
|
||||
&__bold { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
&__break-row { |
||||
margin-top: 30px; |
||||
} |
||||
|
||||
&__body { |
||||
position: relative; |
||||
display: flex; |
||||
max-width: 730px; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
&__body-text { |
||||
max-width: 548px; |
||||
margin-left: 16px; |
||||
margin-right: 16px; |
||||
} |
||||
|
||||
&__bottom-text { |
||||
margin-top: 10px; |
||||
color: #9a9a9a; |
||||
} |
||||
|
||||
&__content { |
||||
overflow-y: auto; |
||||
} |
||||
|
||||
&__footer { |
||||
margin-top: 26px; |
||||
|
||||
@media screen and (max-width: 575px) { |
||||
margin-top: 10px; |
||||
justify-content: center; |
||||
margin-left: 2%; |
||||
max-height: 520px; |
||||
} |
||||
|
||||
.page-container__footer { |
||||
border-top: none; |
||||
max-width: 535px; |
||||
margin-bottom: 15px; |
||||
|
||||
button { |
||||
height: 44px; |
||||
min-height: 44px; |
||||
margin-right: 16px; |
||||
} |
||||
|
||||
header { |
||||
padding: 0px; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,169 @@ |
||||
import React, { Component } from 'react' |
||||
import PropTypes from 'prop-types' |
||||
import PageContainerFooter from '../../../page-container/page-container-footer' |
||||
|
||||
export default class MetaMetricsOptIn extends Component { |
||||
static propTypes = { |
||||
history: PropTypes.object, |
||||
setParticipateInMetaMetrics: PropTypes.func, |
||||
nextRoute: PropTypes.string, |
||||
firstTimeSelectionMetaMetricsName: PropTypes.string, |
||||
participateInMetaMetrics: PropTypes.bool, |
||||
} |
||||
|
||||
static contextTypes = { |
||||
metricsEvent: PropTypes.func, |
||||
} |
||||
|
||||
render () { |
||||
const { metricsEvent } = this.context |
||||
const { |
||||
nextRoute, |
||||
history, |
||||
setParticipateInMetaMetrics, |
||||
firstTimeSelectionMetaMetricsName, |
||||
participateInMetaMetrics, |
||||
} = this.props |
||||
|
||||
return ( |
||||
<div className="metametrics-opt-in"> |
||||
<div className="metametrics-opt-in__main"> |
||||
<div className="app-header__logo-container"> |
||||
<img |
||||
className="app-header__metafox-logo app-header__metafox-logo--horizontal" |
||||
src="/images/logo/metamask-logo-horizontal.svg" |
||||
height={30} |
||||
/> |
||||
<img |
||||
className="app-header__metafox-logo app-header__metafox-logo--icon" |
||||
src="/images/logo/metamask-fox.svg" |
||||
height={42} |
||||
width={42} |
||||
/> |
||||
</div> |
||||
<div className="metametrics-opt-in__body-graphic"> |
||||
<img src="images/metrics-chart.svg" /> |
||||
</div> |
||||
<div className="metametrics-opt-in__title">Help Us Improve MetaMask</div> |
||||
<div className="metametrics-opt-in__body"> |
||||
<div className="metametrics-opt-in__description"> |
||||
MetaMask would like to gather usage data to better understand how our users interact with the extension. This data |
||||
will be used to continually improve the usability and user experience of our product and the Etheruem ecosystem. |
||||
</div> |
||||
<div className="metametrics-opt-in__description"> |
||||
MetaMask will.. |
||||
</div> |
||||
|
||||
<div className="metametrics-opt-in__committments"> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-check" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
Always allow you to opt-out via Settings |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-check" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
Send anonymized click & pageview events |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-check" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
Maintain a public aggregate dashboard to educate the community |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row metametrics-opt-in__break-row"> |
||||
<i className="fa fa-times" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
<span className="metametrics-opt-in__bold">Never</span> collect keys, addresses, transactions, balances, hashes, or any personal information |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-times" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
<span className="metametrics-opt-in__bold">Never</span> collect your full IP address |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__row"> |
||||
<i className="fa fa-times" /> |
||||
<div className="metametrics-opt-in__row-description"> |
||||
<span className="metametrics-opt-in__bold">Never</span> sell data for profit. Ever! |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div className="metametrics-opt-in__footer"> |
||||
<PageContainerFooter |
||||
onCancel={() => { |
||||
setParticipateInMetaMetrics(false) |
||||
.then(() => { |
||||
if (participateInMetaMetrics === null) { |
||||
return metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Onboarding', |
||||
action: 'Metrics Option', |
||||
name: 'Metrics Opt Out', |
||||
}, |
||||
isOptIn: true, |
||||
}, { |
||||
excludeMetaMetricsId: true, |
||||
}) |
||||
.then(() => { |
||||
history.push(nextRoute) |
||||
}) |
||||
} |
||||
}) |
||||
}} |
||||
cancelText={'No Thanks'} |
||||
hideCancel={false} |
||||
onSubmit={() => { |
||||
setParticipateInMetaMetrics(true) |
||||
.then(([participateStatus, metaMetricsId]) => { |
||||
const promise = participateInMetaMetrics === null |
||||
? metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Onboarding', |
||||
action: 'Metrics Option', |
||||
name: 'Metrics Opt In', |
||||
}, |
||||
isOptIn: true, |
||||
}) |
||||
: Promise.resolve() |
||||
|
||||
promise |
||||
.then(() => { |
||||
return metricsEvent({ |
||||
eventOpts: { |
||||
category: 'Onboarding', |
||||
action: 'Import or Create', |
||||
name: firstTimeSelectionMetaMetricsName, |
||||
}, |
||||
isOptIn: true, |
||||
metaMetricsId, |
||||
}) |
||||
}) |
||||
.then(() => { |
||||
history.push(nextRoute) |
||||
}) |
||||
}) |
||||
}} |
||||
submitText={'I agree'} |
||||
submitButtonType={'confirm'} |
||||
disabled={false} |
||||
/> |
||||
<div className="metametrics-opt-in__bottom-text"> |
||||
This data is aggregated and is therefore anonymous for the purposes of General Data Protection Regulation (EU) 2016/679. For more information in relation to our privacy practices, please see our <a |
||||
href="https://metamask.io/privacy.html" |
||||
target="_blank" |
||||
rel="noopener noreferrer" |
||||
> |
||||
Privacy Policy here |
||||
</a>. |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,27 @@ |
||||
import { connect } from 'react-redux' |
||||
import MetaMetricsOptIn from './metametrics-opt-in.component' |
||||
import { setParticipateInMetaMetrics } from '../../../../actions' |
||||
import { getFirstTimeFlowTypeRoute } from '../first-time-flow.selectors' |
||||
|
||||
const firstTimeFlowTypeNameMap = { |
||||
create: 'Selected Create New Wallet', |
||||
'import': 'Selected Import Wallet', |
||||
} |
||||
|
||||
const mapStateToProps = (state) => { |
||||
const { firstTimeFlowType, participateInMetaMetrics } = state.metamask |
||||
|
||||
return { |
||||
nextRoute: getFirstTimeFlowTypeRoute(state), |
||||
firstTimeSelectionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], |
||||
participateInMetaMetrics, |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
setParticipateInMetaMetrics: (val) => dispatch(setParticipateInMetaMetrics(val)), |
||||
} |
||||
} |
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MetaMetricsOptIn) |
@ -1 +1 @@ |
||||
export { default } from './select-action.component' |
||||
export { default } from './select-action.container' |
||||
|
@ -0,0 +1,23 @@ |
||||
import { connect } from 'react-redux' |
||||
import { withRouter } from 'react-router-dom' |
||||
import { compose } from 'recompose' |
||||
import { setFirstTimeFlowType } from '../../../../actions' |
||||
import { getFirstTimeFlowTypeRoute } from '../first-time-flow.selectors' |
||||
import Welcome from './select-action.component' |
||||
|
||||
const mapStateToProps = (state) => { |
||||
return { |
||||
nextRoute: getFirstTimeFlowTypeRoute(state), |
||||
} |
||||
} |
||||
|
||||
const mapDispatchToProps = dispatch => { |
||||
return { |
||||
setFirstTimeFlowType: type => dispatch(setFirstTimeFlowType(type)), |
||||
} |
||||
} |
||||
|
||||
export default compose( |
||||
withRouter, |
||||
connect(mapStateToProps, mapDispatchToProps) |
||||
)(Welcome) |
@ -0,0 +1,106 @@ |
||||
import { Component } from 'react' |
||||
import { connect } from 'react-redux' |
||||
import PropTypes from 'prop-types' |
||||
import { withRouter } from 'react-router-dom' |
||||
import { compose } from 'recompose' |
||||
import { |
||||
getCurrentNetworkId, |
||||
getSelectedAsset, |
||||
getAccountType, |
||||
getNumberOfAccounts, |
||||
getNumberOfTokens, |
||||
} from '../selectors' |
||||
import { |
||||
txDataSelector, |
||||
} from '../selectors/confirm-transaction' |
||||
import { getEnvironmentType } from '../../../app/scripts/lib/util' |
||||
import { |
||||
sendMetaMetricsEvent, |
||||
sendCountIsTrackable, |
||||
} from './metametrics.util' |
||||
|
||||
class MetaMetricsProvider extends Component { |
||||
static propTypes = { |
||||
network: PropTypes.string.isRequired, |
||||
environmentType: PropTypes.string.isRequired, |
||||
activeCurrency: PropTypes.string.isRequired, |
||||
accountType: PropTypes.string.isRequired, |
||||
metaMetricsSendCount: PropTypes.number.isRequired, |
||||
children: PropTypes.object.isRequired, |
||||
history: PropTypes.object.isRequired, |
||||
} |
||||
|
||||
static childContextTypes = { |
||||
metricsEvent: PropTypes.func, |
||||
} |
||||
|
||||
constructor (props) { |
||||
super(props) |
||||
|
||||
this.state = { |
||||
previousPath: '', |
||||
currentPath: window.location.href, |
||||
} |
||||
|
||||
props.history.listen(locationObj => { |
||||
this.setState({ |
||||
previousPath: this.state.currentPath, |
||||
currentPath: window.location.href, |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
getChildContext () { |
||||
const props = this.props |
||||
const { pathname } = location |
||||
const { previousPath, currentPath } = this.state |
||||
|
||||
return { |
||||
metricsEvent: (config = {}, overrides = {}) => { |
||||
const { eventOpts = {} } = config |
||||
const { name = '' } = eventOpts |
||||
const { pathname: overRidePathName = '' } = overrides |
||||
const isSendFlow = Boolean(name.match(/^send|^confirm/) || overRidePathName.match(/send|confirm/)) |
||||
|
||||
if (props.participateInMetaMetrics || config.isOptIn) { |
||||
return sendMetaMetricsEvent({ |
||||
...props, |
||||
...config, |
||||
previousPath, |
||||
currentPath, |
||||
pathname, |
||||
excludeMetaMetricsId: isSendFlow && !sendCountIsTrackable(props.metaMetricsSendCount + 1), |
||||
...overrides, |
||||
}) |
||||
} |
||||
}, |
||||
} |
||||
} |
||||
|
||||
render () { |
||||
return this.props.children |
||||
} |
||||
} |
||||
|
||||
const mapStateToProps = state => { |
||||
const txData = txDataSelector(state) || {} |
||||
|
||||
return { |
||||
network: getCurrentNetworkId(state), |
||||
environmentType: getEnvironmentType(), |
||||
activeCurrency: getSelectedAsset(state), |
||||
accountType: getAccountType(state), |
||||
confirmTransactionOrigin: txData.origin, |
||||
metaMetricsId: state.metamask.metaMetricsId, |
||||
participateInMetaMetrics: state.metamask.participateInMetaMetrics, |
||||
metaMetricsSendCount: state.metamask.metaMetricsSendCount, |
||||
numberOfTokens: getNumberOfTokens(state), |
||||
numberOfAccounts: getNumberOfAccounts(state), |
||||
} |
||||
} |
||||
|
||||
module.exports = compose( |
||||
withRouter, |
||||
connect(mapStateToProps) |
||||
)(MetaMetricsProvider) |
||||
|
@ -0,0 +1,188 @@ |
||||
/* eslint camelcase: 0 */ |
||||
|
||||
const ethUtil = require('ethereumjs-util') |
||||
|
||||
const inDevelopment = process.env.METAMETRICS_URL === 'development' |
||||
|
||||
const METAMETRICS_BASE_URL = 'https://chromeextensionmm.innocraft.cloud/piwik.php' |
||||
const METAMETRICS_REQUIRED_PARAMS = `?idsite=${inDevelopment ? 1 : 2}&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' |
||||
|
||||
const METAMETRICS_CUSTOM_HAD_ERROR = 'hadError' |
||||
const METAMETRICS_CUSTOM_HEX_DATA = 'hexData' |
||||
const METAMETRICS_CUSTOM_FUNCTION_TYPE = 'functionType' |
||||
const METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE = 'gasLimitChange' |
||||
const METAMETRICS_CUSTOM_GAS_PRICE_CHANGE = 'gasPriceChange' |
||||
const METAMETRICS_CUSTOM_RECIPIENT_KNOWN = 'recipientKnown' |
||||
const METAMETRICS_CUSTOM_CONFIRM_SCREEN_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_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_TOKENS = 'numberOfTokens' |
||||
const METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS = 'numberOfAccounts' |
||||
|
||||
const customVariableNameIdMap = { |
||||
[METAMETRICS_CUSTOM_HAD_ERROR]: 1, |
||||
[METAMETRICS_CUSTOM_HEX_DATA]: 2, |
||||
[METAMETRICS_CUSTOM_FUNCTION_TYPE]: 3, |
||||
[METAMETRICS_CUSTOM_GAS_LIMIT_CHANGE]: 4, |
||||
[METAMETRICS_CUSTOM_GAS_PRICE_CHANGE]: 5, |
||||
[METAMETRICS_CUSTOM_RECIPIENT_KNOWN]: 6, |
||||
[METAMETRICS_CUSTOM_CONFIRM_SCREEN_ORIGIN]: 7, |
||||
[METAMETRICS_CUSTOM_FROM_NETWORK]: 8, |
||||
[METAMETRICS_CUSTOM_TO_NETWORK]: 9, |
||||
[METAMETRICS_CUSTOM_RPC_NETWORK_ID]: 10, |
||||
[METAMETRICS_CUSTOM_RPC_CHAIN_ID]: 11, |
||||
[METAMETRICS_CUSTOM_ERROR_FIELD]: 12, |
||||
[METAMETRICS_CUSTOM_ERROR_MESSAGE]: 13, |
||||
} |
||||
|
||||
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_TOKENS]: 9, |
||||
[METAMETRICS_CUSTOM_NUMBER_OF_ACCOUNTS]: 10, |
||||
} |
||||
|
||||
function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) { |
||||
const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'MetaMask' |
||||
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(previousPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` |
||||
} |
||||
|
||||
function composeCustomDimensionParamAddition (customDimensions) { |
||||
const customDimensionParamStrings = Object.keys(customDimensions).reduce((acc, name) => { |
||||
return [...acc, `dimension${customDimensionsNameIdMap[name]}=${customDimensions[name]}`] |
||||
}, []) |
||||
return `&${customDimensionParamStrings.join('&')}` |
||||
} |
||||
|
||||
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}` |
||||
} |
||||
|
||||
function composeUrl (config, permissionPreferences = {}) { |
||||
const { |
||||
eventOpts = {}, |
||||
customVariables = '', |
||||
pageOpts = '', |
||||
network, |
||||
environmentType, |
||||
activeCurrency, |
||||
accountType, |
||||
numberOfTokens, |
||||
numberOfAccounts, |
||||
previousPath = '', |
||||
currentPath, |
||||
metaMetricsId, |
||||
confirmTransactionOrigin, |
||||
url: configUrl, |
||||
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, |
||||
numberOfTokens: customVariables && customVariables.numberOfTokens || numberOfTokens, |
||||
numberOfAccounts: customVariables && customVariables.numberOfAccounts || numberOfAccounts, |
||||
}) : '' |
||||
const url = configUrl || `&url=${encodeURIComponent(currentPath.replace(/chrome-extension:\/\/\w+/, METAMETRICS_TRACKING_URL))}` |
||||
const _id = metaMetricsId && !excludeMetaMetricsId ? `&_id=${metaMetricsId.slice(2, 18)}` : '' |
||||
const rand = `&rand=${String(Math.random()).slice(2)}` |
||||
const pv_id = `&pv_id=${ethUtil.bufferToHex(ethUtil.sha3(url || currentPath.match(/chrome-extension:\/\/\w+\/(.+)/)[0])).slice(2, 8)}` |
||||
const uid = metaMetricsId && !excludeMetaMetricsId |
||||
? `&uid=${metaMetricsId.slice(2, 18)}` |
||||
: excludeMetaMetricsId |
||||
? '&uid=0000000000000000' |
||||
: '' |
||||
|
||||
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, permissionPreferences) { |
||||
return fetch(composeUrl(config, permissionPreferences), { |
||||
'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 |
||||
} else { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
const trackableSendCounts = { |
||||
1: true, |
||||
10: true, |
||||
30: true, |
||||
50: true, |
||||
100: true, |
||||
250: true, |
||||
500: true, |
||||
1000: true, |
||||
2500: true, |
||||
5000: true, |
||||
10000: true, |
||||
25000: true, |
||||
} |
||||
|
||||
export function sendCountIsTrackable (sendCount) { |
||||
return Boolean(trackableSendCounts[sendCount]) |
||||
} |
Loading…
Reference in new issue