mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
consolidate segment setup (#9617)
Consolidates the background and UI segment implementations into a shared solution. This results in the introduction of our first shared module. Co-authored-by: Erik Marks <25517051+rekmarks@users.noreply.github.com>
This commit is contained in:
parent
b369a68eb3
commit
e5688c024e
@ -76,7 +76,7 @@ export default class TransactionController extends EventEmitter {
|
||||
this.blockTracker = opts.blockTracker
|
||||
this.signEthTx = opts.signTransaction
|
||||
this.inProcessOfSigning = new Set()
|
||||
this._trackSegmentEvent = opts.trackSegmentEvent
|
||||
this._trackMetaMetricsEvent = opts.trackMetaMetricsEvent
|
||||
this._getParticipateInMetrics = opts.getParticipateInMetrics
|
||||
|
||||
this.memStore = new ObservableStore({})
|
||||
@ -829,13 +829,13 @@ export default class TransactionController extends EventEmitter {
|
||||
_trackSwapsMetrics (txMeta, approvalTxMeta) {
|
||||
if (this._getParticipateInMetrics() && txMeta.swapMetaData) {
|
||||
if (txMeta.txReceipt.status === '0x0') {
|
||||
this._trackSegmentEvent({
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Swap Failed',
|
||||
category: 'swaps',
|
||||
excludeMetaMetricsId: false,
|
||||
})
|
||||
|
||||
this._trackSegmentEvent({
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Swap Failed',
|
||||
properties: { ...txMeta.swapMetaData },
|
||||
category: 'swaps',
|
||||
@ -865,13 +865,13 @@ export default class TransactionController extends EventEmitter {
|
||||
.round(2)
|
||||
}%`
|
||||
|
||||
this._trackSegmentEvent({
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Swap Completed',
|
||||
category: 'swaps',
|
||||
excludeMetaMetricsId: false,
|
||||
})
|
||||
|
||||
this._trackSegmentEvent({
|
||||
this._trackMetaMetricsEvent({
|
||||
event: 'Swap Completed',
|
||||
category: 'swaps',
|
||||
properties: {
|
||||
|
@ -1,3 +1,12 @@
|
||||
/**
|
||||
* A string representing the type of environment the application is currently running in
|
||||
* popup - When the user click's the icon in their browser's extension bar; the default view
|
||||
* notification - When the extension opens due to interaction with a Web3 enabled website
|
||||
* fullscreen - When the user clicks 'expand view' to open the extension in a new tab
|
||||
* background - The background process that powers the extension
|
||||
* @typedef {'popup' | 'notification' | 'fullscreen' | 'background'} EnvironmentType
|
||||
*/
|
||||
|
||||
const ENVIRONMENT_TYPE_POPUP = 'popup'
|
||||
const ENVIRONMENT_TYPE_NOTIFICATION = 'notification'
|
||||
const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen'
|
||||
|
@ -50,7 +50,12 @@ function logWeb3UsageHandler (
|
||||
event: `Website Accessed window.web3`,
|
||||
category: 'inpage_provider',
|
||||
properties: { action, web3Property: name },
|
||||
referrerUrl: origin,
|
||||
eventContext: {
|
||||
referrer: {
|
||||
url: origin,
|
||||
},
|
||||
},
|
||||
excludeMetaMetricsId: true,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1,112 +0,0 @@
|
||||
import Analytics from 'analytics-node'
|
||||
|
||||
const inDevelopment = process.env.METAMASK_DEBUG || process.env.IN_TEST
|
||||
|
||||
const flushAt = inDevelopment ? 1 : undefined
|
||||
|
||||
const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000'
|
||||
|
||||
const segmentNoop = {
|
||||
track () {
|
||||
// noop
|
||||
},
|
||||
page () {
|
||||
// noop
|
||||
},
|
||||
identify () {
|
||||
// noop
|
||||
},
|
||||
}
|
||||
|
||||
// We do not want to track events on development builds unless specifically
|
||||
// provided a SEGMENT_WRITE_KEY. This also holds true for test environments and
|
||||
// E2E, which is handled in the build process by never providing the SEGMENT_WRITE_KEY
|
||||
// which process.env.IN_TEST is true
|
||||
const segment = process.env.SEGMENT_WRITE_KEY
|
||||
? new Analytics(process.env.SEGMENT_WRITE_KEY, { flushAt })
|
||||
: segmentNoop
|
||||
|
||||
/**
|
||||
* Returns a function for tracking Segment events.
|
||||
*
|
||||
* @param {string} metamaskVersion - The current version of the MetaMask
|
||||
* extension.
|
||||
* @param {Function} getParticipateInMetrics - A function that returns
|
||||
* whether the user participates in MetaMetrics.
|
||||
* @param {Function} getMetricsState - A function for getting state relevant
|
||||
* to MetaMetrics/Segment.
|
||||
*/
|
||||
export function getTrackSegmentEvent (
|
||||
metamaskVersion,
|
||||
getParticipateInMetrics,
|
||||
getMetricsState,
|
||||
) {
|
||||
const version = process.env.METAMASK_ENVIRONMENT === 'production'
|
||||
? metamaskVersion
|
||||
: `${metamaskVersion}-${process.env.METAMASK_ENVIRONMENT}`
|
||||
|
||||
const segmentContext = {
|
||||
app: {
|
||||
name: 'MetaMask Extension',
|
||||
version,
|
||||
},
|
||||
page: {
|
||||
path: '/background-process',
|
||||
title: 'Background Process',
|
||||
url: '/background-process',
|
||||
},
|
||||
userAgent: window.navigator.userAgent,
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks a Segment event per the given arguments.
|
||||
*
|
||||
* @param {string} event - The event name.
|
||||
* @param {string} category - The event category.
|
||||
* @param {Object} [properties] - The event properties.
|
||||
* @param {string} [referrerUrl] - The event's referrer URL, if relevant.
|
||||
* @param {boolean} [excludeMetaMetricsId] - `true` if the user's MetaMetrics id should
|
||||
* not be included, and `false` otherwise. Default: `true`
|
||||
*/
|
||||
return function trackSegmentEvent ({
|
||||
event,
|
||||
category,
|
||||
properties = {},
|
||||
excludeMetaMetricsId = true,
|
||||
referrerUrl,
|
||||
}) {
|
||||
if (!event || !category) {
|
||||
throw new Error('Must specify event and category.')
|
||||
}
|
||||
|
||||
if (!getParticipateInMetrics()) {
|
||||
return
|
||||
}
|
||||
|
||||
const { currentLocale, metaMetricsId } = getMetricsState()
|
||||
|
||||
const trackOptions = {
|
||||
event,
|
||||
category,
|
||||
context: {
|
||||
...segmentContext,
|
||||
locale: currentLocale.replace('_', '-'),
|
||||
},
|
||||
properties,
|
||||
}
|
||||
|
||||
if (excludeMetaMetricsId) {
|
||||
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID
|
||||
} else {
|
||||
trackOptions.userId = metaMetricsId
|
||||
}
|
||||
|
||||
if (referrerUrl) {
|
||||
trackOptions.context.referrer = {
|
||||
url: referrerUrl,
|
||||
}
|
||||
}
|
||||
|
||||
segment.track(trackOptions)
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import {
|
||||
CurrencyRateController,
|
||||
PhishingController,
|
||||
} from '@metamask/controllers'
|
||||
import { getTrackMetaMetricsEvent } from '../../shared/modules/metametrics'
|
||||
import ComposableObservableStore from './lib/ComposableObservableStore'
|
||||
import AccountTracker from './lib/account-tracker'
|
||||
import createLoggerMiddleware from './lib/createLoggerMiddleware'
|
||||
@ -55,9 +56,9 @@ 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 { getTrackSegmentEvent } from './lib/segment'
|
||||
|
||||
import backgroundMetaMetricsEvent from './lib/background-metametrics'
|
||||
import { ENVIRONMENT_TYPE_BACKGROUND } from './lib/enums'
|
||||
|
||||
export default class MetamaskController extends EventEmitter {
|
||||
|
||||
@ -115,16 +116,32 @@ export default class MetamaskController extends EventEmitter {
|
||||
migrateAddressBookState: this.migrateAddressBookState.bind(this),
|
||||
})
|
||||
|
||||
// This depends on preferences controller state
|
||||
this.trackSegmentEvent = getTrackSegmentEvent(
|
||||
this.trackMetaMetricsEvent = getTrackMetaMetricsEvent(
|
||||
this.platform.getVersion(),
|
||||
() => this.preferencesController.getParticipateInMetaMetrics(),
|
||||
() => {
|
||||
const participateInMetaMetrics = this.preferencesController.getParticipateInMetaMetrics()
|
||||
const {
|
||||
currentLocale,
|
||||
metaMetricsId,
|
||||
} = this.preferencesController.store.getState()
|
||||
return { currentLocale, metaMetricsId }
|
||||
const chainId = this.networkController.getCurrentChainId()
|
||||
const provider = this.networkController.getProviderConfig()
|
||||
const network = provider.type === 'rpc' ? provider.rpcUrl : provider.type
|
||||
return {
|
||||
participateInMetaMetrics,
|
||||
metaMetricsId,
|
||||
environmentType: ENVIRONMENT_TYPE_BACKGROUND,
|
||||
chainId,
|
||||
network,
|
||||
context: {
|
||||
page: {
|
||||
path: '/background-process',
|
||||
title: 'Background Process',
|
||||
url: '/background-process',
|
||||
},
|
||||
locale: currentLocale.replace('_', '-'),
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@ -252,7 +269,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
signTransaction: this.keyringController.signTransaction.bind(this.keyringController),
|
||||
provider: this.provider,
|
||||
blockTracker: this.blockTracker,
|
||||
trackSegmentEvent: this.trackSegmentEvent,
|
||||
trackMetaMetricsEvent: this.trackMetaMetricsEvent,
|
||||
getParticipateInMetrics: () => this.preferencesController.getParticipateInMetaMetrics(),
|
||||
})
|
||||
this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx())
|
||||
@ -1671,7 +1688,7 @@ export default class MetamaskController extends EventEmitter {
|
||||
}))
|
||||
engine.push(createMethodMiddleware({
|
||||
origin,
|
||||
sendMetrics: this.trackSegmentEvent,
|
||||
sendMetrics: this.trackMetaMetricsEvent,
|
||||
}))
|
||||
// filter and subscription polyfills
|
||||
engine.push(filterMiddleware)
|
||||
|
219
shared/modules/metametrics.js
Normal file
219
shared/modules/metametrics.js
Normal file
@ -0,0 +1,219 @@
|
||||
import Analytics from 'analytics-node'
|
||||
import { omit, pick } from 'lodash'
|
||||
|
||||
// flushAt controls how many events are collected in the queue before they
|
||||
// are sent to segment. I recommend a queue size of one due to an issue with
|
||||
// detecting and flushing events in an extension beforeunload doesn't work in
|
||||
// a notification context. Because notification windows are opened and closed
|
||||
// in reaction to the very events we want to track, it is problematic to cache
|
||||
// at all.
|
||||
const flushAt = 1
|
||||
|
||||
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000'
|
||||
|
||||
const segmentNoop = {
|
||||
track () {
|
||||
// noop
|
||||
},
|
||||
page () {
|
||||
// noop
|
||||
},
|
||||
identify () {
|
||||
// noop
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine whether or not to attach a user's metametrics id
|
||||
* to events that include on-chain data. This helps to prevent identifying
|
||||
* a user by being able to trace their activity on etherscan/block exploring
|
||||
*/
|
||||
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])
|
||||
}
|
||||
|
||||
// We do not want to track events on development builds unless specifically
|
||||
// provided a SEGMENT_WRITE_KEY. This also holds true for test environments and
|
||||
// E2E, which is handled in the build process by never providing the SEGMENT_WRITE_KEY
|
||||
// when process.env.IN_TEST is truthy
|
||||
export const segment = process.env.SEGMENT_WRITE_KEY
|
||||
? new Analytics(process.env.SEGMENT_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
|
||||
* method call. Ideally app and userAgent are defined on every event. This is confirmed
|
||||
* in the getTrackMetaMetricsEvent function, but still provides the consumer a way to
|
||||
* override these values if necessary.
|
||||
* @typedef {Object} MetaMetricsContext
|
||||
* @property {Object} app
|
||||
* @property {string} app.name - the name of the application tracking the event
|
||||
* @property {string} app.version - the version of the application
|
||||
* @property {string} userAgent - the useragent string of the user
|
||||
* @property {string} locale - the locale string for the user
|
||||
* @property {Object} [page] - an object representing details of the current page
|
||||
* @property {string} [page.path] - the path of the current page (e.g /home)
|
||||
* @property {string} [page.title] - the title of the current page (e.g 'home')
|
||||
* @property {string} [page.url] - the fully qualified url of the current page
|
||||
* @property {Object} [referrer] - for metamask, this is the dapp that triggered an interaction
|
||||
* @property {string} [referrer.url] - the origin of the dapp issuing the notification
|
||||
*/
|
||||
|
||||
/**
|
||||
* page and referrer from the MetaMetricsContext are very dynamic in nature and may be
|
||||
* provided as part of the initial context payload when creating the trackMetaMetricsEvent function,
|
||||
* or at the event level when calling the trackMetaMetricsEvent function.
|
||||
* @typedef {Pick<MetaMetricsContext, 'page' | 'referrer'>} MetaMetricsDynamicContext
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {import('../../app/scripts/lib/enums').EnvironmentType} EnvironmentType
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetaMetricsRequiredState
|
||||
* @property {bool} participateInMetaMetrics - has the user opted into metametrics
|
||||
* @property {string} [metaMetricsId] - the user's metaMetricsId, if they have opted in
|
||||
* @property {MetaMetricsDynamicContext} context - context about the event
|
||||
* @property {string} chainId - the chain id of the current network
|
||||
* @property {string} locale - the locale string of the current user
|
||||
* @property {string} network - the name of the current network
|
||||
* @property {EnvironmentType} environmentType - environment that the event happened in
|
||||
* @property {string} [metaMetricsSendCount] - number of transactions sent, used to add metametricsId
|
||||
* intermittently to events with onchain data attached to them used to protect identity of users.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MetaMetricsEventPayload
|
||||
* @property {string} event - event name to track
|
||||
* @property {string} category - category to associate event to
|
||||
* @property {boolean} [isOptIn] - happened during opt in/out workflow
|
||||
* @property {object} [properties] - object of custom values to track, snake_case
|
||||
* @property {number} [revenue] - amount of currency that event creates in revenue for MetaMask
|
||||
* @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 {MetaMetricsDynamicContext} [eventContext] - additional context to attach to event
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns a function for tracking Segment events.
|
||||
*
|
||||
* @param {string} metamaskVersion - The current version of the MetaMask extension.
|
||||
* @param {() => MetaMetricsRequiredState} getDynamicState - A function returning required fields
|
||||
* @returns {(payload: MetaMetricsEventPayload) => Promise<void>} - function to track an event
|
||||
*/
|
||||
export function getTrackMetaMetricsEvent (
|
||||
metamaskVersion,
|
||||
getDynamicState,
|
||||
) {
|
||||
const version = process.env.METAMASK_ENVIRONMENT === 'production'
|
||||
? metamaskVersion
|
||||
: `${metamaskVersion}-${process.env.METAMASK_ENVIRONMENT}`
|
||||
|
||||
return function trackMetaMetricsEvent ({
|
||||
event,
|
||||
category,
|
||||
isOptIn,
|
||||
properties = {},
|
||||
revenue,
|
||||
currency,
|
||||
value,
|
||||
excludeMetaMetricsId: excludeId,
|
||||
eventContext = {},
|
||||
}) {
|
||||
if (!event || !category) {
|
||||
throw new Error('Must specify event and category.')
|
||||
}
|
||||
const {
|
||||
participateInMetaMetrics,
|
||||
context: providedContext,
|
||||
metaMetricsId,
|
||||
environmentType,
|
||||
chainId,
|
||||
locale,
|
||||
network,
|
||||
metaMetricsSendCount,
|
||||
} = getDynamicState()
|
||||
|
||||
let excludeMetaMetricsId = excludeId ?? false
|
||||
|
||||
// This is carried over from the old implementation, and will likely need
|
||||
// to be updated to work with the new tracking plan. I think we should use
|
||||
// a config setting for this instead of trying to match the event name
|
||||
const isSendFlow = Boolean(event.match(/^send|^confirm/u))
|
||||
if (isSendFlow && metaMetricsSendCount && !sendCountIsTrackable(metaMetricsSendCount + 1)) {
|
||||
excludeMetaMetricsId = true
|
||||
}
|
||||
|
||||
if (!participateInMetaMetrics && !isOptIn) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** @type {MetaMetricsContext} */
|
||||
const context = {
|
||||
app: {
|
||||
name: 'MetaMask Extension',
|
||||
version,
|
||||
},
|
||||
locale,
|
||||
userAgent: window.navigator.userAgent,
|
||||
...pick(providedContext, ['page', 'referrer']),
|
||||
...pick(eventContext, ['page', 'referrer']),
|
||||
}
|
||||
|
||||
const trackOptions = {
|
||||
event,
|
||||
properties: {
|
||||
// These values are omitted from properties because they have special meaning
|
||||
// in segment. https://segment.com/docs/connections/spec/track/#properties.
|
||||
// to avoid accidentally using these inappropriately, you must add them as top
|
||||
// level properties on the event payload.
|
||||
...omit(properties, ['revenue', 'currency', 'value']),
|
||||
revenue,
|
||||
value,
|
||||
currency,
|
||||
category,
|
||||
network,
|
||||
chain_id: chainId,
|
||||
environment_type: environmentType,
|
||||
},
|
||||
context,
|
||||
}
|
||||
|
||||
if (excludeMetaMetricsId) {
|
||||
trackOptions.anonymousId = METAMETRICS_ANONYMOUS_ID
|
||||
} else {
|
||||
trackOptions.userId = metaMetricsId
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// This is only safe to do because we are no longer batching events through segment.
|
||||
// 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) => {
|
||||
if (err) {
|
||||
return reject(err)
|
||||
}
|
||||
return resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
@ -17,8 +17,8 @@ import {
|
||||
import { getEnvironmentType } from '../../../app/scripts/lib/util'
|
||||
import {
|
||||
sendMetaMetricsEvent,
|
||||
sendCountIsTrackable,
|
||||
} from '../helpers/utils/metametrics.util'
|
||||
import { sendCountIsTrackable } from '../../../shared/modules/metametrics'
|
||||
|
||||
export const MetaMetricsContext = createContext(() => {
|
||||
captureException(
|
||||
|
@ -3,26 +3,19 @@
|
||||
* MetaMetrics is our own brand, and should remain aptly named regardless of the underlying
|
||||
* metrics system. This file implements Segment analytics tracking.
|
||||
*/
|
||||
import React, { useRef, Component, createContext, useEffect, useCallback } from 'react'
|
||||
import React, { useRef, Component, createContext, useEffect, useMemo } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import PropTypes from 'prop-types'
|
||||
import { useLocation, matchPath, useRouteMatch } from 'react-router-dom'
|
||||
import { captureException, captureMessage } from '@sentry/browser'
|
||||
|
||||
import { omit } from 'lodash'
|
||||
import {
|
||||
getCurrentNetworkId,
|
||||
} from '../selectors/selectors'
|
||||
|
||||
import { getEnvironmentType } from '../../../app/scripts/lib/util'
|
||||
import {
|
||||
sendCountIsTrackable,
|
||||
segment,
|
||||
METAMETRICS_ANONYMOUS_ID,
|
||||
} from '../helpers/utils/metametrics.util'
|
||||
import { PATH_NAME_MAP } from '../helpers/constants/routes'
|
||||
import { getCurrentLocale } from '../ducks/metamask/metamask'
|
||||
import { txDataSelector } from '../selectors'
|
||||
import { getCurrentChainId, getMetricsNetworkIdentifier, txDataSelector } from '../selectors'
|
||||
import { getTrackMetaMetricsEvent, METAMETRICS_ANONYMOUS_ID, segment } from '../../../shared/modules/metametrics'
|
||||
|
||||
export const MetaMetricsContext = createContext(() => {
|
||||
captureException(
|
||||
@ -34,7 +27,6 @@ const PATHS_TO_CHECK = Object.keys(PATH_NAME_MAP)
|
||||
|
||||
function useSegmentContext () {
|
||||
const match = useRouteMatch({ path: PATHS_TO_CHECK, exact: true, strict: true })
|
||||
const locale = useSelector(getCurrentLocale)
|
||||
const txData = useSelector(txDataSelector) || {}
|
||||
const confirmTransactionOrigin = txData.origin
|
||||
|
||||
@ -42,11 +34,6 @@ function useSegmentContext () {
|
||||
url: confirmTransactionOrigin,
|
||||
} : undefined
|
||||
|
||||
let version = global.platform.getVersion()
|
||||
if (process.env.METAMASK_ENVIRONMENT !== 'production') {
|
||||
version = `${version}-${process.env.METAMASK_ENVIRONMENT}`
|
||||
}
|
||||
|
||||
const page = match ? {
|
||||
path: match.path,
|
||||
title: PATH_NAME_MAP[match.path],
|
||||
@ -54,24 +41,39 @@ function useSegmentContext () {
|
||||
} : undefined
|
||||
|
||||
return {
|
||||
app: {
|
||||
version,
|
||||
name: 'MetaMask Extension',
|
||||
},
|
||||
locale: locale.replace('_', '-'),
|
||||
page,
|
||||
referrer,
|
||||
userAgent: window.navigator.userAgent,
|
||||
}
|
||||
}
|
||||
|
||||
export function MetaMetricsProvider ({ children }) {
|
||||
const network = useSelector(getCurrentNetworkId)
|
||||
const metaMetricsId = useSelector((state) => state.metamask.metaMetricsId)
|
||||
const participateInMetaMetrics = useSelector((state) => state.metamask.participateInMetaMetrics)
|
||||
const metaMetricsSendCount = useSelector((state) => state.metamask.metaMetricsSendCount)
|
||||
const locale = useSelector(getCurrentLocale)
|
||||
const location = useLocation()
|
||||
const context = useSegmentContext()
|
||||
const network = useSelector(getMetricsNetworkIdentifier)
|
||||
const chainId = useSelector(getCurrentChainId)
|
||||
|
||||
/**
|
||||
* track a metametrics event
|
||||
*
|
||||
* @param {import('../../../shared/modules/metametrics').MetaMetricsEventPayload} - payload for event
|
||||
* @returns undefined
|
||||
*/
|
||||
const trackEvent = useMemo(() => {
|
||||
return getTrackMetaMetricsEvent(global.platform.getVersion(), () => ({
|
||||
context,
|
||||
locale: locale.replace('_', '-'),
|
||||
environmentType: getEnvironmentType(),
|
||||
chainId,
|
||||
network,
|
||||
participateInMetaMetrics,
|
||||
metaMetricsId,
|
||||
metaMetricsSendCount,
|
||||
}))
|
||||
}, [network, participateInMetaMetrics, locale, metaMetricsId, metaMetricsSendCount, chainId, context])
|
||||
|
||||
// Used to prevent double tracking page calls
|
||||
const previousMatch = useRef()
|
||||
@ -131,72 +133,6 @@ export function MetaMetricsProvider ({ children }) {
|
||||
}
|
||||
}, [location, context, network, metaMetricsId, participateInMetaMetrics])
|
||||
|
||||
/**
|
||||
* track a metametrics event using segment
|
||||
* e.g metricsEvent({ event: 'Unlocked MetaMask', category: 'Navigation' })
|
||||
*
|
||||
* @param {object} config - configuration object for the event to track
|
||||
* @param {string} config.event - event name to track
|
||||
* @param {string} config.category - category to associate event to
|
||||
* @param {boolean} [config.isOptIn] - happened during opt in/out workflow
|
||||
* @param {object} [config.properties] - object of custom values to track, snake_case
|
||||
* @param {number} [config.revenue] - amount of currency that event creates in revenue for MetaMask
|
||||
* @param {string} [config.currency] - ISO 4127 format currency for events with revenue, defaults to US dollars
|
||||
* @param {number} [config.value] - Abstract "value" that this event has for MetaMask.
|
||||
* @return {undefined}
|
||||
*/
|
||||
const trackEvent = useCallback(
|
||||
(config = {}) => {
|
||||
const { event, category, isOptIn = false, properties = {}, revenue, value, currency } = config
|
||||
if (!event) {
|
||||
// Event name is required for tracking an event
|
||||
throw new Error('MetaMetrics trackEvent function must be provided a payload with an "event" key')
|
||||
}
|
||||
if (!category) {
|
||||
// Category must be supplied for every tracking event
|
||||
throw new Error('MetaMetrics events must be provided a category')
|
||||
}
|
||||
const environmentType = getEnvironmentType()
|
||||
|
||||
let excludeMetaMetricsId = config.excludeMetaMetricsId ?? false
|
||||
|
||||
// This is carried over from the old implementation, and will likely need
|
||||
// to be updated to work with the new tracking plan. I think we should use
|
||||
// a config setting for this instead of trying to match the event name
|
||||
const isSendFlow = Boolean(event.match(/^send|^confirm/u))
|
||||
if (isSendFlow && !sendCountIsTrackable(metaMetricsSendCount + 1)) {
|
||||
excludeMetaMetricsId = true
|
||||
}
|
||||
const idTrait = excludeMetaMetricsId ? 'anonymousId' : 'userId'
|
||||
const idValue = excludeMetaMetricsId ? METAMETRICS_ANONYMOUS_ID : metaMetricsId
|
||||
|
||||
if (participateInMetaMetrics || isOptIn) {
|
||||
segment.track({
|
||||
[idTrait]: idValue,
|
||||
event,
|
||||
properties: {
|
||||
...omit(properties, ['revenue', 'currency', 'value']),
|
||||
revenue,
|
||||
value,
|
||||
currency,
|
||||
category,
|
||||
network,
|
||||
environment_type: environmentType,
|
||||
},
|
||||
context,
|
||||
})
|
||||
}
|
||||
|
||||
return undefined
|
||||
}, [
|
||||
context,
|
||||
network,
|
||||
metaMetricsId,
|
||||
metaMetricsSendCount,
|
||||
participateInMetaMetrics,
|
||||
],
|
||||
)
|
||||
|
||||
return (
|
||||
<MetaMetricsContext.Provider value={trackEvent}>
|
||||
{children}
|
||||
|
@ -1,7 +1,6 @@
|
||||
/* eslint camelcase: 0 */
|
||||
|
||||
import ethUtil from 'ethereumjs-util'
|
||||
import Analytics from 'analytics-node'
|
||||
|
||||
const inDevelopment = process.env.METAMASK_DEBUG || process.env.IN_TEST
|
||||
|
||||
@ -74,8 +73,6 @@ const customDimensionsNameIdMap = {
|
||||
[METAMETRICS_CUSTOM_VERSION]: 11,
|
||||
}
|
||||
|
||||
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000'
|
||||
|
||||
function composeUrlRefParamAddition (previousPath, confirmTransactionOrigin) {
|
||||
const externalOrigin = confirmTransactionOrigin && confirmTransactionOrigin !== 'metamask'
|
||||
return `&urlref=${externalOrigin ? 'EXTERNAL' : encodeURIComponent(`${METAMETRICS_TRACKING_URL}${previousPath}`)}`
|
||||
@ -217,43 +214,3 @@ export function verifyUserPermission (config, props) {
|
||||
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])
|
||||
}
|
||||
|
||||
const flushAt = inDevelopment ? 1 : undefined
|
||||
|
||||
const segmentNoop = {
|
||||
track () {
|
||||
// noop
|
||||
},
|
||||
page () {
|
||||
// noop
|
||||
},
|
||||
identify () {
|
||||
// noop
|
||||
},
|
||||
}
|
||||
|
||||
// We do not want to track events on development builds unless specifically
|
||||
// provided a SEGMENT_WRITE_KEY. This also holds true for test environments and
|
||||
// E2E, which is handled in the build process by never providing the SEGMENT_WRITE_KEY
|
||||
// which process.env.IN_TEST is true
|
||||
export const segment = process.env.SEGMENT_WRITE_KEY
|
||||
? new Analytics(process.env.SEGMENT_WRITE_KEY, { flushAt })
|
||||
: segmentNoop
|
||||
|
@ -14,6 +14,16 @@ export function getNetworkIdentifier (state) {
|
||||
return nickname || rpcUrl || type
|
||||
}
|
||||
|
||||
export function getMetricsNetworkIdentifier (state) {
|
||||
const { provider } = state.metamask
|
||||
return provider.type === 'rpc' ? provider.rpcUrl : provider.type
|
||||
}
|
||||
|
||||
export function getCurrentChainId (state) {
|
||||
const { chainId } = state.metamask.provider
|
||||
return chainId
|
||||
}
|
||||
|
||||
export function getCurrentKeyring (state) {
|
||||
const identity = getSelectedIdentity(state)
|
||||
|
||||
@ -156,7 +166,7 @@ export function getAssetImages (state) {
|
||||
}
|
||||
|
||||
export function getAddressBook (state) {
|
||||
const { chainId } = state.metamask.provider
|
||||
const chainId = getCurrentChainId(state)
|
||||
if (!state.metamask.addressBook[chainId]) {
|
||||
return []
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user