Merge pull request #9976 from MetaMask/Version-v8.1.6
Version v8.1.6 RCfeature/default_network_editable
commit
52e428fbe6
@ -1,41 +0,0 @@ |
|||||||
const path = require('path') |
|
||||||
|
|
||||||
const CopyWebpackPlugin = require('copy-webpack-plugin') |
|
||||||
|
|
||||||
module.exports = { |
|
||||||
module: { |
|
||||||
strictExportPresence: true, |
|
||||||
rules: [ |
|
||||||
{ |
|
||||||
test: /\.scss$/, |
|
||||||
loaders: [ |
|
||||||
'style-loader', |
|
||||||
{ |
|
||||||
loader: 'css-loader', |
|
||||||
options: { |
|
||||||
import: false, |
|
||||||
url: false, |
|
||||||
}, |
|
||||||
}, |
|
||||||
'resolve-url-loader', |
|
||||||
{ |
|
||||||
loader: 'sass-loader', |
|
||||||
options: { |
|
||||||
sourceMap: true, |
|
||||||
}, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
plugins: [ |
|
||||||
new CopyWebpackPlugin({ |
|
||||||
patterns: [ |
|
||||||
{ |
|
||||||
from: path.join('node_modules', '@fortawesome', 'fontawesome-free', 'webfonts'), |
|
||||||
to: path.join('fonts', 'fontawesome'), |
|
||||||
}, |
|
||||||
], |
|
||||||
}), |
|
||||||
], |
|
||||||
} |
|
After Width: | Height: | Size: 2.4 KiB |
@ -0,0 +1,366 @@ |
|||||||
|
import { merge, omit } from 'lodash' |
||||||
|
import ObservableStore from 'obs-store' |
||||||
|
import { bufferToHex, sha3 } from 'ethereumjs-util' |
||||||
|
import { ENVIRONMENT_TYPE_BACKGROUND } from '../lib/enums' |
||||||
|
import { |
||||||
|
METAMETRICS_ANONYMOUS_ID, |
||||||
|
METAMETRICS_BACKGROUND_PAGE_OBJECT, |
||||||
|
} from '../../../shared/constants/metametrics' |
||||||
|
|
||||||
|
/** |
||||||
|
* 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]) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsContext} MetaMetricsContext |
||||||
|
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventPayload} MetaMetricsEventPayload |
||||||
|
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsEventOptions} MetaMetricsEventOptions |
||||||
|
* @typedef {import('../../../shared/constants/metametrics').SegmentEventPayload} SegmentEventPayload |
||||||
|
* @typedef {import('../../../shared/constants/metametrics').SegmentInterface} SegmentInterface |
||||||
|
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPagePayload} MetaMetricsPagePayload |
||||||
|
* @typedef {import('../../../shared/constants/metametrics').MetaMetricsPageOptions} MetaMetricsPageOptions |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} MetaMetricsControllerState |
||||||
|
* @property {?string} metaMetricsId - The user's metaMetricsId that will be |
||||||
|
* attached to all non-anonymized event payloads |
||||||
|
* @property {?boolean} participateInMetaMetrics - The user's preference for |
||||||
|
* participating in the MetaMetrics analytics program. This setting controls |
||||||
|
* whether or not events are tracked |
||||||
|
* @property {number} metaMetricsSendCount - How many send transactions have |
||||||
|
* been tracked through this controller. Used to prevent attaching sensitive |
||||||
|
* data that can be traced through on chain data. |
||||||
|
*/ |
||||||
|
|
||||||
|
export default class MetaMetricsController { |
||||||
|
/** |
||||||
|
* @param {Object} segment - an instance of analytics-node for tracking |
||||||
|
* events that conform to the new MetaMetrics tracking plan. |
||||||
|
* @param {Object} segmentLegacy - an instance of analytics-node for |
||||||
|
* tracking legacy schema events. Will eventually be phased out |
||||||
|
* @param {Object} preferencesStore - The preferences controller store, used |
||||||
|
* to access and subscribe to preferences that will be attached to events |
||||||
|
* @param {function} onNetworkDidChange - Used to attach a listener to the |
||||||
|
* networkDidChange event emitted by the networkController |
||||||
|
* @param {function} getCurrentChainId - Gets the current chain id from the |
||||||
|
* network controller |
||||||
|
* @param {function} getNetworkIdentifier - Gets the current network |
||||||
|
* identifier from the network controller |
||||||
|
* @param {string} version - The version of the extension |
||||||
|
* @param {string} environment - The environment the extension is running in |
||||||
|
* @param {MetaMetricsControllerState} initState - State to initialized with |
||||||
|
*/ |
||||||
|
constructor({ |
||||||
|
segment, |
||||||
|
segmentLegacy, |
||||||
|
preferencesStore, |
||||||
|
onNetworkDidChange, |
||||||
|
getCurrentChainId, |
||||||
|
getNetworkIdentifier, |
||||||
|
version, |
||||||
|
environment, |
||||||
|
initState, |
||||||
|
}) { |
||||||
|
const prefState = preferencesStore.getState() |
||||||
|
this.chainId = getCurrentChainId() |
||||||
|
this.network = getNetworkIdentifier() |
||||||
|
this.locale = prefState.currentLocale.replace('_', '-') |
||||||
|
this.version = |
||||||
|
environment === 'production' ? version : `${version}-${environment}` |
||||||
|
|
||||||
|
this.store = new ObservableStore({ |
||||||
|
participateInMetaMetrics: null, |
||||||
|
metaMetricsId: null, |
||||||
|
metaMetricsSendCount: 0, |
||||||
|
...initState, |
||||||
|
}) |
||||||
|
|
||||||
|
preferencesStore.subscribe(({ currentLocale }) => { |
||||||
|
this.locale = currentLocale.replace('_', '-') |
||||||
|
}) |
||||||
|
|
||||||
|
onNetworkDidChange(() => { |
||||||
|
this.chainId = getCurrentChainId() |
||||||
|
this.network = getNetworkIdentifier() |
||||||
|
}) |
||||||
|
this.segment = segment |
||||||
|
this.segmentLegacy = segmentLegacy |
||||||
|
} |
||||||
|
|
||||||
|
generateMetaMetricsId() { |
||||||
|
return bufferToHex( |
||||||
|
sha3( |
||||||
|
String(Date.now()) + |
||||||
|
String(Math.round(Math.random() * Number.MAX_SAFE_INTEGER)), |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Setter for the `participateInMetaMetrics` property |
||||||
|
* |
||||||
|
* @param {boolean} participateInMetaMetrics - Whether or not the user wants |
||||||
|
* to participate in MetaMetrics |
||||||
|
* @returns {string|null} the string of the new metametrics id, or null |
||||||
|
* if not set |
||||||
|
*/ |
||||||
|
setParticipateInMetaMetrics(participateInMetaMetrics) { |
||||||
|
let { metaMetricsId } = this.state |
||||||
|
if (participateInMetaMetrics && !metaMetricsId) { |
||||||
|
metaMetricsId = this.generateMetaMetricsId() |
||||||
|
} else if (participateInMetaMetrics === false) { |
||||||
|
metaMetricsId = null |
||||||
|
} |
||||||
|
this.store.updateState({ participateInMetaMetrics, metaMetricsId }) |
||||||
|
return metaMetricsId |
||||||
|
} |
||||||
|
|
||||||
|
get state() { |
||||||
|
return this.store.getState() |
||||||
|
} |
||||||
|
|
||||||
|
setMetaMetricsSendCount(val) { |
||||||
|
this.store.updateState({ metaMetricsSendCount: val }) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Build the context object to attach to page and track events. |
||||||
|
* @private |
||||||
|
* @param {Pick<MetaMetricsContext, 'referrer'>} [referrer] - dapp origin that initialized |
||||||
|
* the notification window. |
||||||
|
* @param {Pick<MetaMetricsContext, 'page'>} [page] - page object describing the current |
||||||
|
* view of the extension. Defaults to the background-process object. |
||||||
|
* @returns {MetaMetricsContext} |
||||||
|
*/ |
||||||
|
_buildContext(referrer, page = METAMETRICS_BACKGROUND_PAGE_OBJECT) { |
||||||
|
return { |
||||||
|
app: { |
||||||
|
name: 'MetaMask Extension', |
||||||
|
version: this.version, |
||||||
|
}, |
||||||
|
userAgent: window.navigator.userAgent, |
||||||
|
page, |
||||||
|
referrer, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Build's the event payload, processing all fields into a format that can be |
||||||
|
* fed to Segment's track method |
||||||
|
* @private |
||||||
|
* @param { |
||||||
|
* Omit<MetaMetricsEventPayload, 'sensitiveProperties'> |
||||||
|
* } rawPayload - raw payload provided to trackEvent |
||||||
|
* @returns {SegmentEventPayload} - formatted event payload for segment |
||||||
|
*/ |
||||||
|
_buildEventPayload(rawPayload) { |
||||||
|
const { |
||||||
|
event, |
||||||
|
properties, |
||||||
|
revenue, |
||||||
|
value, |
||||||
|
currency, |
||||||
|
category, |
||||||
|
page, |
||||||
|
referrer, |
||||||
|
environmentType = ENVIRONMENT_TYPE_BACKGROUND, |
||||||
|
} = rawPayload |
||||||
|
return { |
||||||
|
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. We also exclude locale to prevent consumers
|
||||||
|
// from overwriting this context level property. We track it as a property
|
||||||
|
// because not all destinations map locale from context.
|
||||||
|
...omit(properties, ['revenue', 'locale', 'currency', 'value']), |
||||||
|
revenue, |
||||||
|
value, |
||||||
|
currency, |
||||||
|
category, |
||||||
|
network: this.network, |
||||||
|
locale: this.locale, |
||||||
|
chain_id: this.chainId, |
||||||
|
environment_type: environmentType, |
||||||
|
}, |
||||||
|
context: this._buildContext(referrer, page), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Perform validation on the payload and update the id type to use before |
||||||
|
* sending to Segment. Also examines the options to route and handle the |
||||||
|
* event appropriately. |
||||||
|
* @private |
||||||
|
* @param {SegmentEventPayload} payload - properties to attach to event |
||||||
|
* @param {MetaMetricsEventOptions} [options] - options for routing and |
||||||
|
* handling the event |
||||||
|
* @returns {Promise<void>} |
||||||
|
*/ |
||||||
|
_track(payload, options) { |
||||||
|
const { |
||||||
|
isOptIn, |
||||||
|
metaMetricsId: metaMetricsIdOverride, |
||||||
|
matomoEvent, |
||||||
|
flushImmediately, |
||||||
|
} = options || {} |
||||||
|
let idType = 'userId' |
||||||
|
let idValue = this.state.metaMetricsId |
||||||
|
let excludeMetaMetricsId = options?.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(payload.event.match(/^send|^confirm/iu)) |
||||||
|
if ( |
||||||
|
isSendFlow && |
||||||
|
this.state.metaMetricsSendCount && |
||||||
|
!sendCountIsTrackable(this.state.metaMetricsSendCount + 1) |
||||||
|
) { |
||||||
|
excludeMetaMetricsId = true |
||||||
|
} |
||||||
|
// 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 || (isOptIn && !metaMetricsIdOverride)) { |
||||||
|
idType = 'anonymousId' |
||||||
|
idValue = METAMETRICS_ANONYMOUS_ID |
||||||
|
} else if (isOptIn && metaMetricsIdOverride) { |
||||||
|
idValue = metaMetricsIdOverride |
||||||
|
} |
||||||
|
payload[idType] = idValue |
||||||
|
|
||||||
|
// Promises will only resolve when the event is sent to segment. For any
|
||||||
|
// event that relies on this promise being fulfilled before performing UI
|
||||||
|
// updates, or otherwise delaying user interaction, supply the
|
||||||
|
// 'flushImmediately' flag to the trackEvent method.
|
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const callback = (err) => { |
||||||
|
if (err) { |
||||||
|
return reject(err) |
||||||
|
} |
||||||
|
return resolve() |
||||||
|
} |
||||||
|
|
||||||
|
const target = matomoEvent === true ? this.segmentLegacy : this.segment |
||||||
|
|
||||||
|
target.track(payload, callback) |
||||||
|
if (flushImmediately) { |
||||||
|
target.flush() |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* track a page view with Segment |
||||||
|
* @param {MetaMetricsPagePayload} payload - details of the page viewed |
||||||
|
* @param {MetaMetricsPageOptions} [options] - options for handling the page |
||||||
|
* view |
||||||
|
*/ |
||||||
|
trackPage({ name, params, environmentType, page, referrer }, options) { |
||||||
|
if (this.state.participateInMetaMetrics === false) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if (this.state.participateInMetaMetrics === null && !options?.isOptInPath) { |
||||||
|
return |
||||||
|
} |
||||||
|
const { metaMetricsId } = this.state |
||||||
|
const idTrait = metaMetricsId ? 'userId' : 'anonymousId' |
||||||
|
const idValue = metaMetricsId ?? METAMETRICS_ANONYMOUS_ID |
||||||
|
this.segment.page({ |
||||||
|
[idTrait]: idValue, |
||||||
|
name, |
||||||
|
properties: { |
||||||
|
params, |
||||||
|
locale: this.locale, |
||||||
|
network: this.network, |
||||||
|
chain_id: this.chainId, |
||||||
|
environment_type: environmentType, |
||||||
|
}, |
||||||
|
context: this._buildContext(referrer, page), |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* track a metametrics event, performing necessary payload manipulation and |
||||||
|
* routing the event to the appropriate segment source. Will split events |
||||||
|
* with sensitiveProperties into two events, tracking the sensitiveProperties |
||||||
|
* with the anonymousId only. |
||||||
|
* @param {MetaMetricsEventPayload} payload - details of the event |
||||||
|
* @param {MetaMetricsEventOptions} [options] - options for handling/routing the event |
||||||
|
* @returns {Promise<void>} |
||||||
|
*/ |
||||||
|
async trackEvent(payload, options) { |
||||||
|
// event and category are required fields for all payloads
|
||||||
|
if (!payload.event || !payload.category) { |
||||||
|
throw new Error('Must specify event and category.') |
||||||
|
} |
||||||
|
|
||||||
|
if (!this.state.participateInMetaMetrics && !options?.isOptIn) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// We might track multiple events if sensitiveProperties is included, this array will hold
|
||||||
|
// the promises returned from this._track.
|
||||||
|
const events = [] |
||||||
|
|
||||||
|
if (payload.sensitiveProperties) { |
||||||
|
// sensitiveProperties will only be tracked using the anonymousId property and generic id
|
||||||
|
// If the event options already specify to exclude the metaMetricsId we throw an error as
|
||||||
|
// a signal to the developer that the event was implemented incorrectly
|
||||||
|
if (options?.excludeMetaMetricsId === true) { |
||||||
|
throw new Error( |
||||||
|
'sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag', |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const combinedProperties = merge( |
||||||
|
payload.sensitiveProperties, |
||||||
|
payload.properties, |
||||||
|
) |
||||||
|
|
||||||
|
events.push( |
||||||
|
this._track( |
||||||
|
this._buildEventPayload({ |
||||||
|
...payload, |
||||||
|
properties: combinedProperties, |
||||||
|
}), |
||||||
|
{ ...options, excludeMetaMetricsId: true }, |
||||||
|
), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
events.push(this._track(this._buildEventPayload(payload), options)) |
||||||
|
|
||||||
|
await Promise.all(events) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
import setupSentry from './lib/setupSentry' |
||||||
|
|
||||||
|
// setup sentry error reporting
|
||||||
|
global.sentry = setupSentry({ |
||||||
|
release: process.env.METAMASK_VERSION, |
||||||
|
getState: () => global.getSentryState?.() || {}, |
||||||
|
}) |
@ -1,37 +0,0 @@ |
|||||||
/** |
|
||||||
* Freezes the Promise global and prevents its reassignment. |
|
||||||
*/ |
|
||||||
import deepFreeze from 'deep-freeze-strict' |
|
||||||
|
|
||||||
if (process.env.IN_TEST !== 'true' && process.env.METAMASK_ENV !== 'test') { |
|
||||||
freeze(global, 'Promise') |
|
||||||
} |
|
||||||
|
|
||||||
/** |
|
||||||
* Makes a key:value pair on a target object immutable, with limitations. |
|
||||||
* The key cannot be reassigned or deleted, and the value is recursively frozen |
|
||||||
* using Object.freeze. |
|
||||||
* |
|
||||||
* Because of JavaScript language limitations, this is does not mean that the |
|
||||||
* value is completely immutable. It is, however, better than nothing. |
|
||||||
* |
|
||||||
* @param {Object} target - The target object to freeze a property on. |
|
||||||
* @param {string} key - The key to freeze. |
|
||||||
* @param {any} [value] - The value to freeze, if different from the existing value on the target. |
|
||||||
* @param {boolean} [enumerable=true] - If given a value, whether the property is enumerable. |
|
||||||
*/ |
|
||||||
function freeze(target, key, value, enumerable = true) { |
|
||||||
const opts = { |
|
||||||
configurable: false, |
|
||||||
writable: false, |
|
||||||
} |
|
||||||
|
|
||||||
if (value === undefined) { |
|
||||||
target[key] = deepFreeze(target[key]) |
|
||||||
} else { |
|
||||||
opts.value = deepFreeze(value) |
|
||||||
opts.enumerable = enumerable |
|
||||||
} |
|
||||||
|
|
||||||
Object.defineProperty(target, key, opts) |
|
||||||
} |
|
@ -1,4 +1,5 @@ |
|||||||
import logWeb3Usage from './log-web3-usage' |
import logWeb3Usage from './log-web3-usage' |
||||||
|
import watchAsset from './watch-asset' |
||||||
|
|
||||||
const handlers = [logWeb3Usage] |
const handlers = [logWeb3Usage, watchAsset] |
||||||
export default handlers |
export default handlers |
||||||
|
@ -0,0 +1,40 @@ |
|||||||
|
import { MESSAGE_TYPE } from '../../enums' |
||||||
|
|
||||||
|
const watchAsset = { |
||||||
|
methodNames: [MESSAGE_TYPE.WATCH_ASSET, MESSAGE_TYPE.WATCH_ASSET_LEGACY], |
||||||
|
implementation: watchAssetHandler, |
||||||
|
} |
||||||
|
export default watchAsset |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} WatchAssetOptions |
||||||
|
* @property {Function} handleWatchAssetRequest - The wallet_watchAsset method implementation. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} WatchAssetParam |
||||||
|
* @property {string} type - The type of the asset to watch. |
||||||
|
* @property {Object} options - Watch options for the asset. |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @param {import('json-rpc-engine').JsonRpcRequest<WatchAssetParam>} req - The JSON-RPC request object. |
||||||
|
* @param {import('json-rpc-engine').JsonRpcResponse<true>} res - The JSON-RPC response object. |
||||||
|
* @param {Function} _next - The json-rpc-engine 'next' callback. |
||||||
|
* @param {Function} end - The json-rpc-engine 'end' callback. |
||||||
|
* @param {WatchAssetOptions} options |
||||||
|
*/ |
||||||
|
async function watchAssetHandler( |
||||||
|
req, |
||||||
|
res, |
||||||
|
_next, |
||||||
|
end, |
||||||
|
{ handleWatchAssetRequest }, |
||||||
|
) { |
||||||
|
try { |
||||||
|
res.result = await handleWatchAssetRequest(req) |
||||||
|
return end() |
||||||
|
} catch (error) { |
||||||
|
return end(error) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,101 @@ |
|||||||
|
import Analytics from 'analytics-node' |
||||||
|
|
||||||
|
const isDevOrTestEnvironment = Boolean( |
||||||
|
process.env.METAMASK_DEBUG || process.env.IN_TEST, |
||||||
|
) |
||||||
|
const SEGMENT_WRITE_KEY = process.env.SEGMENT_WRITE_KEY ?? null |
||||||
|
const SEGMENT_LEGACY_WRITE_KEY = process.env.SEGMENT_LEGACY_WRITE_KEY ?? null |
||||||
|
const SEGMENT_HOST = process.env.SEGMENT_HOST ?? null |
||||||
|
|
||||||
|
// flushAt controls how many events are sent to segment at once. Segment will
|
||||||
|
// hold onto a queue of events until it hits this number, then it sends them as
|
||||||
|
// a batch. This setting defaults to 20, but in development we likely want to
|
||||||
|
// see events in real time for debugging, so this is set to 1 to disable the
|
||||||
|
// queueing mechanism.
|
||||||
|
const SEGMENT_FLUSH_AT = |
||||||
|
process.env.METAMASK_ENVIRONMENT === 'production' ? undefined : 1 |
||||||
|
|
||||||
|
// flushInterval controls how frequently the queue is flushed to segment.
|
||||||
|
// This happens regardless of the size of the queue. The default setting is
|
||||||
|
// 10,000ms (10 seconds). This default is rather high, though thankfully
|
||||||
|
// using the background process as our event handler means we don't have to
|
||||||
|
// deal with short lived sessions that happen faster than the interval
|
||||||
|
// e.g confirmations. This is set to 5,000ms (5 seconds) arbitrarily with the
|
||||||
|
// intent of having a value less than 10 seconds.
|
||||||
|
const SEGMENT_FLUSH_INTERVAL = 5000 |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a mock segment module for usage in test environments. This is used |
||||||
|
* when building the application in test mode to catch event calls and prevent |
||||||
|
* them from being sent to segment. It is also used in unit tests to mock and |
||||||
|
* spy on the methods to ensure proper behavior |
||||||
|
* @param {number} flushAt - number of events to queue before sending to segment |
||||||
|
* @param {number} flushInterval - ms interval to flush queue and send to segment |
||||||
|
* @returns {SegmentInterface} |
||||||
|
*/ |
||||||
|
export const createSegmentMock = ( |
||||||
|
flushAt = SEGMENT_FLUSH_AT, |
||||||
|
flushInterval = SEGMENT_FLUSH_INTERVAL, |
||||||
|
) => { |
||||||
|
const segmentMock = { |
||||||
|
// Internal queue to keep track of events and properly mimic segment's
|
||||||
|
// queueing behavior.
|
||||||
|
queue: [], |
||||||
|
|
||||||
|
/** |
||||||
|
* Used to immediately send all queued events and reset the queue to zero. |
||||||
|
* For our purposes this simply triggers the callback method registered with |
||||||
|
* the event. |
||||||
|
*/ |
||||||
|
flush() { |
||||||
|
segmentMock.queue.forEach(([_, callback]) => { |
||||||
|
callback() |
||||||
|
}) |
||||||
|
segmentMock.queue = [] |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* Track an event and add it to the queue. If the queue size reaches the |
||||||
|
* flushAt threshold, flush the queue. |
||||||
|
*/ |
||||||
|
track(payload, callback = () => undefined) { |
||||||
|
segmentMock.queue.push([payload, callback]) |
||||||
|
|
||||||
|
if (segmentMock.queue.length >= flushAt) { |
||||||
|
segmentMock.flush() |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
/** |
||||||
|
* A true NOOP, these methods are either not used or do not await callback |
||||||
|
* and therefore require no functionality. |
||||||
|
*/ |
||||||
|
page() { |
||||||
|
// noop
|
||||||
|
}, |
||||||
|
identify() { |
||||||
|
// noop
|
||||||
|
}, |
||||||
|
} |
||||||
|
// Mimic the flushInterval behavior with an interval
|
||||||
|
setInterval(segmentMock.flush, flushInterval) |
||||||
|
return segmentMock |
||||||
|
} |
||||||
|
|
||||||
|
export const segment = |
||||||
|
!SEGMENT_WRITE_KEY || (isDevOrTestEnvironment && !SEGMENT_HOST) |
||||||
|
? createSegmentMock(SEGMENT_FLUSH_AT, SEGMENT_FLUSH_INTERVAL) |
||||||
|
: new Analytics(SEGMENT_WRITE_KEY, { |
||||||
|
host: SEGMENT_HOST, |
||||||
|
flushAt: SEGMENT_FLUSH_AT, |
||||||
|
flushInterval: SEGMENT_FLUSH_INTERVAL, |
||||||
|
}) |
||||||
|
|
||||||
|
export const segmentLegacy = |
||||||
|
!SEGMENT_LEGACY_WRITE_KEY || (isDevOrTestEnvironment && !SEGMENT_HOST) |
||||||
|
? createSegmentMock(SEGMENT_FLUSH_AT, SEGMENT_FLUSH_INTERVAL) |
||||||
|
: new Analytics(SEGMENT_LEGACY_WRITE_KEY, { |
||||||
|
host: SEGMENT_HOST, |
||||||
|
flushAt: SEGMENT_FLUSH_AT, |
||||||
|
flushInterval: SEGMENT_FLUSH_INTERVAL, |
||||||
|
}) |
@ -0,0 +1,44 @@ |
|||||||
|
import { cloneDeep } from 'lodash' |
||||||
|
|
||||||
|
const version = 49 |
||||||
|
|
||||||
|
/** |
||||||
|
* Migrate metaMetrics state to the new MetaMetrics controller |
||||||
|
*/ |
||||||
|
export default { |
||||||
|
version, |
||||||
|
async migrate(originalVersionedData) { |
||||||
|
const versionedData = cloneDeep(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
const state = versionedData.data |
||||||
|
versionedData.data = transformState(state) |
||||||
|
return versionedData |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState(state = {}) { |
||||||
|
if (state.PreferencesController) { |
||||||
|
const { |
||||||
|
metaMetricsId, |
||||||
|
participateInMetaMetrics, |
||||||
|
metaMetricsSendCount, |
||||||
|
} = state.PreferencesController |
||||||
|
state.MetaMetricsController = state.MetaMetricsController ?? {} |
||||||
|
|
||||||
|
if (metaMetricsId !== undefined) { |
||||||
|
state.MetaMetricsController.metaMetricsId = metaMetricsId |
||||||
|
delete state.PreferencesController.metaMetricsId |
||||||
|
} |
||||||
|
|
||||||
|
if (participateInMetaMetrics !== undefined) { |
||||||
|
state.MetaMetricsController.participateInMetaMetrics = participateInMetaMetrics |
||||||
|
delete state.PreferencesController.participateInMetaMetrics |
||||||
|
} |
||||||
|
|
||||||
|
if (metaMetricsSendCount !== undefined) { |
||||||
|
state.MetaMetricsController.metaMetricsSendCount = metaMetricsSendCount |
||||||
|
delete state.PreferencesController.metaMetricsSendCount |
||||||
|
} |
||||||
|
} |
||||||
|
return state |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
import { cloneDeep } from 'lodash' |
||||||
|
|
||||||
|
const version = 50 |
||||||
|
|
||||||
|
const LEGACY_LOCAL_STORAGE_KEYS = [ |
||||||
|
'METASWAP_GAS_PRICE_ESTIMATES_LAST_RETRIEVED', |
||||||
|
'METASWAP_GAS_PRICE_ESTIMATES', |
||||||
|
'cachedFetch', |
||||||
|
'BASIC_PRICE_ESTIMATES_LAST_RETRIEVED', |
||||||
|
'BASIC_PRICE_ESTIMATES', |
||||||
|
'BASIC_GAS_AND_TIME_API_ESTIMATES', |
||||||
|
'BASIC_GAS_AND_TIME_API_ESTIMATES_LAST_RETRIEVED', |
||||||
|
'GAS_API_ESTIMATES_LAST_RETRIEVED', |
||||||
|
'GAS_API_ESTIMATES', |
||||||
|
] |
||||||
|
|
||||||
|
/** |
||||||
|
* Migrate metaMetrics state to the new MetaMetrics controller |
||||||
|
*/ |
||||||
|
export default { |
||||||
|
version, |
||||||
|
async migrate(originalVersionedData) { |
||||||
|
const versionedData = cloneDeep(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
|
||||||
|
LEGACY_LOCAL_STORAGE_KEYS.forEach((key) => |
||||||
|
window.localStorage.removeItem(key), |
||||||
|
) |
||||||
|
|
||||||
|
return versionedData |
||||||
|
}, |
||||||
|
} |
@ -0,0 +1,7 @@ |
|||||||
|
// Freezes all intrinsics
|
||||||
|
// eslint-disable-next-line no-undef,import/unambiguous
|
||||||
|
lockdown({ |
||||||
|
errorTaming: 'unsafe', |
||||||
|
mathTaming: 'unsafe', |
||||||
|
dateTaming: 'unsafe', |
||||||
|
}) |
@ -0,0 +1,140 @@ |
|||||||
|
// Type Imports
|
||||||
|
/** |
||||||
|
* @typedef {import('../../app/scripts/lib/enums').EnvironmentType} EnvironmentType |
||||||
|
*/ |
||||||
|
|
||||||
|
// Type Declarations
|
||||||
|
/** |
||||||
|
* Used to attach context of where the user was at in the application when the |
||||||
|
* event was triggered. Also included as full details of the current page in |
||||||
|
* page events. |
||||||
|
* @typedef {Object} MetaMetricsPageObject |
||||||
|
* @property {string} [path] - the path of the current page (e.g /home) |
||||||
|
* @property {string} [title] - the title of the current page (e.g 'home') |
||||||
|
* @property {string} [url] - the fully qualified url of the current page |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* For metamask, this is the dapp that triggered an interaction |
||||||
|
* @typedef {Object} MetaMetricsReferrerObject |
||||||
|
* @property {string} [url] - the origin of the dapp issuing the |
||||||
|
* notification |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 {MetaMetricsPageObject} [page] - an object representing details of |
||||||
|
* the current page |
||||||
|
* @property {MetaMetricsReferrerObject} [referrer] - for metamask, this is the |
||||||
|
* dapp that triggered an interaction |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} MetaMetricsEventPayload |
||||||
|
* @property {string} event - event name to track |
||||||
|
* @property {string} category - category to associate event to |
||||||
|
* @property {string} [environmentType] - The type of environment this event |
||||||
|
* occurred in. Defaults to the background process type |
||||||
|
* @property {object} [properties] - object of custom values to track, keys |
||||||
|
* in this object must be in snake_case |
||||||
|
* @property {object} [sensitiveProperties] - Object of sensitive values to |
||||||
|
* track. Keys in this object must be in snake_case. These properties will be |
||||||
|
* sent in an additional event that excludes the user's metaMetricsId |
||||||
|
* @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 business "value" attributable to |
||||||
|
* customers who trigger this event |
||||||
|
* @property {MetaMetricsPageObject} [page] - the page/route that the event |
||||||
|
* occurred on |
||||||
|
* @property {MetaMetricsReferrerObject} [referrer] - the origin of the dapp |
||||||
|
* that triggered the event |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} MetaMetricsEventOptions |
||||||
|
* @property {boolean} [isOptIn] - happened during opt in/out workflow |
||||||
|
* @property {boolean} [flushImmediately] - When true will automatically flush |
||||||
|
* the segment queue after tracking the event. Recommended if the result of |
||||||
|
* tracking the event must be known before UI transition or update |
||||||
|
* @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 schema |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* Represents the shape of data sent to the segment.track method. |
||||||
|
* @typedef {Object} SegmentEventPayload |
||||||
|
* @property {string} [userId] - The metametrics id for the user |
||||||
|
* @property {string} [anonymousId] - An anonymousId that is used to track |
||||||
|
* sensitive data while preserving anonymity. |
||||||
|
* @property {string} event - name of the event to track |
||||||
|
* @property {Object} properties - properties to attach to the event |
||||||
|
* @property {MetaMetricsContext} context - the context the event occurred in |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} MetaMetricsPagePayload |
||||||
|
* @property {string} name - The name of the page that was viewed |
||||||
|
* @property {Object} [params] - The variadic parts of the page url |
||||||
|
* example (route: `/asset/:asset`, path: `/asset/ETH`) |
||||||
|
* params: { asset: 'ETH' } |
||||||
|
* @property {EnvironmentType} environmentType - the environment type that the |
||||||
|
* page was viewed in |
||||||
|
* @property {MetaMetricsPageObject} [page] - the details of the page |
||||||
|
* @property {MetaMetricsReferrerObject} [referrer] - dapp that triggered the page |
||||||
|
* view |
||||||
|
*/ |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} MetaMetricsPageOptions |
||||||
|
* @property {boolean} [isOptInPath] - is the current path one of the pages in |
||||||
|
* the onboarding workflow? If true and participateInMetaMetrics is null track |
||||||
|
* the page view |
||||||
|
*/ |
||||||
|
|
||||||
|
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000' |
||||||
|
|
||||||
|
/** |
||||||
|
* This object is used to identify events that are triggered by the background |
||||||
|
* process. |
||||||
|
* @type {MetaMetricsPageObject} |
||||||
|
*/ |
||||||
|
export const METAMETRICS_BACKGROUND_PAGE_OBJECT = { |
||||||
|
path: '/background-process', |
||||||
|
title: 'Background Process', |
||||||
|
url: '/background-process', |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @typedef {Object} SegmentInterface |
||||||
|
* @property {SegmentEventPayload[]} queue - A queue of events to be sent when |
||||||
|
* the flushAt limit has been reached, or flushInterval occurs |
||||||
|
* @property {() => void} flush - Immediately flush the queue, resetting it to |
||||||
|
* an empty array and sending the pending events to Segment |
||||||
|
* @property {( |
||||||
|
* payload: SegmentEventPayload, |
||||||
|
* callback: (err?: Error) => void |
||||||
|
* ) => void} track - Track an event with Segment, using the internal batching |
||||||
|
* mechanism to optimize network requests |
||||||
|
* @property {(payload: Object) => void} page - Track a page view with Segment |
||||||
|
* @property {() => void} identify - Identify an anonymous user. We do not |
||||||
|
* currently use this method. |
||||||
|
*/ |
@ -0,0 +1,3 @@ |
|||||||
|
### Shared Modules |
||||||
|
|
||||||
|
This folder is reserved for modules that can be used globally within both the background and ui applications. |
@ -1,302 +0,0 @@ |
|||||||
import Analytics from 'analytics-node' |
|
||||||
import { merge, omit, pick } from 'lodash' |
|
||||||
|
|
||||||
// flushAt controls how many events are sent to segment at once. Segment
|
|
||||||
// will hold onto a queue of events until it hits this number, then it sends
|
|
||||||
// them as a batch. This setting defaults to 20, but that is too high for
|
|
||||||
// notification workflows. We also cannot send each event as singular payloads
|
|
||||||
// because it seems to bombard segment and potentially cause event loss.
|
|
||||||
// I chose 5 here because it is sufficiently high enough to optimize our network
|
|
||||||
// requests, while also being low enough to be reasonable.
|
|
||||||
const flushAt = process.env.METAMASK_ENVIRONMENT === 'production' ? 5 : 1 |
|
||||||
// flushInterval controls how frequently the queue is flushed to segment.
|
|
||||||
// This happens regardless of the size of the queue. The default setting is
|
|
||||||
// 10,000ms (10 seconds). This default is absurdly high for our typical user
|
|
||||||
// flow through confirmations. I have chosen 10 ms here because it works really
|
|
||||||
// well with our wrapped track function. The track function returns a promise
|
|
||||||
// that is only fulfilled when it has been sent to segment. A 10 ms delay is
|
|
||||||
// negligible to the user, but allows us to properly batch events that happen
|
|
||||||
// in rapid succession.
|
|
||||||
const flushInterval = 10 |
|
||||||
|
|
||||||
export const METAMETRICS_ANONYMOUS_ID = '0x0000000000000000' |
|
||||||
|
|
||||||
const segmentNoop = { |
|
||||||
track(_, callback = () => undefined) { |
|
||||||
// Need to call the callback so that environments without a segment id still
|
|
||||||
// resolve the promise from trackMetaMetricsEvent
|
|
||||||
return callback() |
|
||||||
}, |
|
||||||
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]) |
|
||||||
} |
|
||||||
|
|
||||||
const isDevOrTestEnvironment = Boolean( |
|
||||||
process.env.METAMASK_DEBUG || process.env.IN_TEST, |
|
||||||
) |
|
||||||
|
|
||||||
// This allows us to overwrite the metric destination for testing purposes
|
|
||||||
const host = process.env.SEGMENT_HOST ?? undefined |
|
||||||
|
|
||||||
// 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 || (isDevOrTestEnvironment && !host) |
|
||||||
? segmentNoop |
|
||||||
: new Analytics(process.env.SEGMENT_WRITE_KEY, { |
|
||||||
host, |
|
||||||
flushAt, |
|
||||||
flushInterval, |
|
||||||
}) |
|
||||||
|
|
||||||
export const segmentLegacy = |
|
||||||
!process.env.SEGMENT_LEGACY_WRITE_KEY || (isDevOrTestEnvironment && !host) |
|
||||||
? segmentNoop |
|
||||||
: new Analytics(process.env.SEGMENT_LEGACY_WRITE_KEY, { |
|
||||||
host, |
|
||||||
flushAt, |
|
||||||
flushInterval, |
|
||||||
}) |
|
||||||
|
|
||||||
/** |
|
||||||
* 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 {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 {object} [sensitiveProperties] - Object of sensitive values to track, snake_case. |
|
||||||
* These properties will be sent in an additional event that excludes the user's metaMetricsId. |
|
||||||
* @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 {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 |
|
||||||
*/ |
|
||||||
|
|
||||||
/** |
|
||||||
* 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 = {}, |
|
||||||
sensitiveProperties, |
|
||||||
revenue, |
|
||||||
currency, |
|
||||||
value, |
|
||||||
metaMetricsId: metaMetricsIdOverride, |
|
||||||
excludeMetaMetricsId: excludeId, |
|
||||||
matomoEvent = false, |
|
||||||
eventContext = {}, |
|
||||||
}) { |
|
||||||
if (!event || !category) { |
|
||||||
throw new Error('Must specify event and category.') |
|
||||||
} |
|
||||||
// Uses recursion to track a duplicate event with sensitive properties included,
|
|
||||||
// but metaMetricsId excluded
|
|
||||||
if (sensitiveProperties) { |
|
||||||
if (excludeId === true) { |
|
||||||
throw new Error( |
|
||||||
'sensitiveProperties was specified in an event payload that also set the excludeMetaMetricsId flag', |
|
||||||
) |
|
||||||
} |
|
||||||
trackMetaMetricsEvent({ |
|
||||||
event, |
|
||||||
category, |
|
||||||
isOptIn, |
|
||||||
properties: merge(sensitiveProperties, properties), |
|
||||||
revenue, |
|
||||||
currency, |
|
||||||
value, |
|
||||||
excludeMetaMetricsId: true, |
|
||||||
matomoEvent, |
|
||||||
eventContext, |
|
||||||
}) |
|
||||||
} |
|
||||||
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, |
|
||||||
}, |
|
||||||
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. We also exclude locale to prevent consumers
|
|
||||||
// from overwriting this context level property. We track it as a property
|
|
||||||
// because not all destinations map locale from context.
|
|
||||||
...omit(properties, ['revenue', 'locale', 'currency', 'value']), |
|
||||||
revenue, |
|
||||||
value, |
|
||||||
currency, |
|
||||||
category, |
|
||||||
network, |
|
||||||
locale, |
|
||||||
chain_id: chainId, |
|
||||||
environment_type: environmentType, |
|
||||||
}, |
|
||||||
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 |
|
||||||
} |
|
||||||
|
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
// This is only safe to do because we have set an extremely low (10ms) flushInterval.
|
|
||||||
const callback = (err) => { |
|
||||||
if (err) { |
|
||||||
return reject(err) |
|
||||||
} |
|
||||||
return resolve() |
|
||||||
} |
|
||||||
|
|
||||||
if (matomoEvent === true) { |
|
||||||
segmentLegacy.track(trackOptions, callback) |
|
||||||
} else { |
|
||||||
segment.track(trackOptions, callback) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
} |
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,168 @@ |
|||||||
|
{ |
||||||
|
"data": { |
||||||
|
"AppStateController": { |
||||||
|
"connectedStatusPopoverHasBeenShown": false, |
||||||
|
"swapsWelcomeMessageHasBeenShown": true |
||||||
|
}, |
||||||
|
"CachedBalancesController": { |
||||||
|
"cachedBalances": { |
||||||
|
"4": {}, |
||||||
|
"1337": { |
||||||
|
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": "0x15af1d78b58c40000" |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"CurrencyController": { |
||||||
|
"conversionDate": 1594348502.519, |
||||||
|
"conversionRate": 240.09, |
||||||
|
"currentCurrency": "usd", |
||||||
|
"nativeCurrency": "ETH" |
||||||
|
}, |
||||||
|
"IncomingTransactionsController": { |
||||||
|
"incomingTransactions": {}, |
||||||
|
"incomingTxLastFetchedBlocksByNetwork": { |
||||||
|
"goerli": null, |
||||||
|
"kovan": null, |
||||||
|
"mainnet": null, |
||||||
|
"rinkeby": 5570536, |
||||||
|
"localhost": 98 |
||||||
|
} |
||||||
|
}, |
||||||
|
"KeyringController": { |
||||||
|
"vault": "{\"data\":\"s6TpYjlUNsn7ifhEFTkuDGBUM1GyOlPrim7JSjtfIxgTt8/6MiXgiR/CtFfR4dWW2xhq85/NGIBYEeWrZThGdKGarBzeIqBfLFhw9n509jprzJ0zc2Rf+9HVFGLw+xxC4xPxgCS0IIWeAJQ+XtGcHmn0UZXriXm8Ja4kdlow6SWinB7sr/WM3R0+frYs4WgllkwggDf2/Tv6VHygvLnhtzp6hIJFyTjh+l/KnyJTyZW1TkZhDaNDzX3SCOHT\",\"iv\":\"FbeHDAW5afeWNORfNJBR0Q==\",\"salt\":\"TxZ+WbCW6891C9LK/hbMAoUsSEW1E8pyGLVBU6x5KR8=\"}" |
||||||
|
}, |
||||||
|
"NetworkController": { |
||||||
|
"provider": { |
||||||
|
"nickname": "Localhost 8545", |
||||||
|
"rpcUrl": "http://localhost:8545", |
||||||
|
"chainId": "0x539", |
||||||
|
"ticker": "ETH", |
||||||
|
"type": "rpc" |
||||||
|
}, |
||||||
|
"network": "1337" |
||||||
|
}, |
||||||
|
"OnboardingController": { |
||||||
|
"onboardingTabs": {}, |
||||||
|
"seedPhraseBackedUp": false |
||||||
|
}, |
||||||
|
"PermissionsMetadata": { |
||||||
|
"permissionsLog": [ |
||||||
|
{ |
||||||
|
"id": 1764280960, |
||||||
|
"method": "eth_requestAccounts", |
||||||
|
"methodType": "restricted", |
||||||
|
"origin": "http://127.0.0.1:8080", |
||||||
|
"request": { |
||||||
|
"method": "eth_requestAccounts", |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"id": 1764280960, |
||||||
|
"origin": "http://127.0.0.1:8080", |
||||||
|
"tabId": 2 |
||||||
|
}, |
||||||
|
"requestTime": 1594348329232, |
||||||
|
"response": { |
||||||
|
"id": 1764280960, |
||||||
|
"jsonrpc": "2.0", |
||||||
|
"result": ["0x5cfe73b6021e818b776b421b1c4db2474086a7e1"] |
||||||
|
}, |
||||||
|
"responseTime": 1594348332276, |
||||||
|
"success": true |
||||||
|
} |
||||||
|
], |
||||||
|
"permissionsHistory": { |
||||||
|
"http://127.0.0.1:8080": { |
||||||
|
"eth_accounts": { |
||||||
|
"accounts": { |
||||||
|
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": 1594348332276 |
||||||
|
}, |
||||||
|
"lastApproved": 1594348332276 |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"domainMetadata": { |
||||||
|
"http://127.0.0.1:8080": { |
||||||
|
"name": "E2E Test Dapp", |
||||||
|
"icon": "http://127.0.0.1:8080/metamask-fox.svg", |
||||||
|
"lastUpdated": 1594348323811, |
||||||
|
"host": "127.0.0.1:8080" |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"PreferencesController": { |
||||||
|
"frequentRpcListDetail": [], |
||||||
|
"accountTokens": { |
||||||
|
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": { |
||||||
|
"rinkeby": [], |
||||||
|
"ropsten": [] |
||||||
|
} |
||||||
|
}, |
||||||
|
"assetImages": {}, |
||||||
|
"tokens": [], |
||||||
|
"suggestedTokens": {}, |
||||||
|
"useBlockie": false, |
||||||
|
"useNonceField": false, |
||||||
|
"usePhishDetect": true, |
||||||
|
"featureFlags": { |
||||||
|
"showIncomingTransactions": true, |
||||||
|
"transactionTime": false |
||||||
|
}, |
||||||
|
"knownMethodData": {}, |
||||||
|
"participateInMetaMetrics": true, |
||||||
|
"firstTimeFlowType": "create", |
||||||
|
"currentLocale": "en", |
||||||
|
"identities": { |
||||||
|
"0x5cfe73b6021e818b776b421b1c4db2474086a7e1": { |
||||||
|
"address": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1", |
||||||
|
"name": "Account 1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"lostIdentities": {}, |
||||||
|
"forgottenPassword": false, |
||||||
|
"preferences": { |
||||||
|
"useNativeCurrencyAsPrimaryCurrency": true |
||||||
|
}, |
||||||
|
"completedOnboarding": true, |
||||||
|
"metaMetricsId": "fake-metrics-id", |
||||||
|
"metaMetricsSendCount": 0, |
||||||
|
"ipfsGateway": "dweb.link", |
||||||
|
"selectedAddress": "0x5cfe73b6021e818b776b421b1c4db2474086a7e1" |
||||||
|
}, |
||||||
|
"config": {}, |
||||||
|
"firstTimeInfo": { |
||||||
|
"date": 1575697234195, |
||||||
|
"version": "7.7.0" |
||||||
|
}, |
||||||
|
"PermissionsController": { |
||||||
|
"permissionsRequests": [], |
||||||
|
"permissionsDescriptions": {}, |
||||||
|
"domains": { |
||||||
|
"http://127.0.0.1:8080": { |
||||||
|
"permissions": [ |
||||||
|
{ |
||||||
|
"@context": ["https://github.com/MetaMask/rpc-cap"], |
||||||
|
"parentCapability": "eth_accounts", |
||||||
|
"id": "f55a1c15-ea48-4088-968e-63be474d42fa", |
||||||
|
"date": 1594348332268, |
||||||
|
"invoker": "http://127.0.0.1:8080", |
||||||
|
"caveats": [ |
||||||
|
{ |
||||||
|
"type": "limitResponseLength", |
||||||
|
"value": 1, |
||||||
|
"name": "primaryAccountOnly" |
||||||
|
}, |
||||||
|
{ |
||||||
|
"type": "filterResponse", |
||||||
|
"value": ["0x5cfe73b6021e818b776b421b1c4db2474086a7e1"], |
||||||
|
"name": "exposedAccounts" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"meta": { |
||||||
|
"version": 47 |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue