1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-23 02:10:12 +01:00
metamask-extension/ui/index.js
Mark Stacey e8b33fb7c8
Restore state snapshot for Sentry errors (#9028)
The state snapshot that was attached to Sentry errors was removed
recently in #8794 because it had become too large. The snapshot has
now been restored and reduced in size.

A utility function has been written to reduce the state object to just
the requested properties. This seemed safer than filtering out state
that is known to be large or to contain identifiable information.
This is not a great solution, as now knowledge about the state shape
resides in this large constant, but it will suffice for now. I am
hopeful that we can decorate our controllers with this metadata in the
future instead, as part of the upcoming background controller refactor.

A separate `getSentryState` global function has been added to get the
reduced state, so that the old `getCleanAppState` function that we used
to use could remain unchanged. It's still useful to get that full state
copy while debugging, and in e2e tests.
2020-07-17 12:09:38 -03:00

211 lines
6.3 KiB
JavaScript

import copyToClipboard from 'copy-to-clipboard'
import log from 'loglevel'
import { clone } from 'lodash'
import React from 'react'
import { render } from 'react-dom'
import Root from './app/pages'
import * as actions from './app/store/actions'
import configureStore from './app/store/store'
import txHelper from './lib/tx-helper'
import { getEnvironmentType } from '../app/scripts/lib/util'
import { ALERT_TYPES } from '../app/scripts/controllers/alert'
import { SENTRY_STATE } from '../app/scripts/lib/setupSentry'
import { ENVIRONMENT_TYPE_POPUP } from '../app/scripts/lib/enums'
import { fetchLocale, loadRelativeTimeFormatLocaleData } from './app/helpers/utils/i18n-helper'
import switchDirection from './app/helpers/utils/switch-direction'
import { getPermittedAccountsForCurrentTab, getSelectedAddress } from './app/selectors'
import { ALERT_STATE } from './app/ducks/alerts/unconnected-account'
import {
getUnconnectedAccountAlertEnabledness,
getUnconnectedAccountAlertShown,
} from './app/ducks/metamask/metamask'
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn')
export default function launchMetamaskUi (opts, cb) {
const { backgroundConnection } = opts
actions._setBackgroundConnection(backgroundConnection)
// check if we are unlocked first
backgroundConnection.getState(function (err, metamaskState) {
if (err) {
return cb(err)
}
startApp(metamaskState, backgroundConnection, opts)
.then((store) => {
setupDebuggingHelpers(store)
cb(null, store)
})
})
}
async function startApp (metamaskState, backgroundConnection, opts) {
// parse opts
if (!metamaskState.featureFlags) {
metamaskState.featureFlags = {}
}
const currentLocaleMessages = metamaskState.currentLocale
? await fetchLocale(metamaskState.currentLocale)
: {}
const enLocaleMessages = await fetchLocale('en')
await loadRelativeTimeFormatLocaleData('en')
if (metamaskState.currentLocale) {
await loadRelativeTimeFormatLocaleData(metamaskState.currentLocale)
}
if (metamaskState.textDirection === 'rtl') {
await switchDirection('rtl')
}
const draftInitialState = {
activeTab: opts.activeTab,
// metamaskState represents the cross-tab state
metamask: metamaskState,
// appState represents the current tab's popup state
appState: {},
localeMessages: {
current: currentLocaleMessages,
en: enLocaleMessages,
},
}
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
const origin = draftInitialState.activeTab.origin
const permittedAccountsForCurrentTab = getPermittedAccountsForCurrentTab(draftInitialState)
const selectedAddress = getSelectedAddress(draftInitialState)
const unconnectedAccountAlertShownOrigins = getUnconnectedAccountAlertShown(draftInitialState)
const unconnectedAccountAlertIsEnabled = getUnconnectedAccountAlertEnabledness(draftInitialState)
if (
origin &&
unconnectedAccountAlertIsEnabled &&
!unconnectedAccountAlertShownOrigins[origin] &&
permittedAccountsForCurrentTab.length > 0 &&
!permittedAccountsForCurrentTab.includes(selectedAddress)
) {
draftInitialState[ALERT_TYPES.unconnectedAccount] = { state: ALERT_STATE.OPEN }
actions.setUnconnectedAccountAlertShown(origin)
}
}
const store = configureStore(draftInitialState)
// if unconfirmed txs, start on txConf page
const unapprovedTxsAll = txHelper(
metamaskState.unapprovedTxs,
metamaskState.unapprovedMsgs,
metamaskState.unapprovedPersonalMsgs,
metamaskState.unapprovedDecryptMsgs,
metamaskState.unapprovedEncryptionPublicKeyMsgs,
metamaskState.unapprovedTypedMessages,
metamaskState.network,
)
const numberOfUnapprivedTx = unapprovedTxsAll.length
if (numberOfUnapprivedTx > 0) {
store.dispatch(actions.showConfTxPage({
id: unapprovedTxsAll[0].id,
}))
}
backgroundConnection.on('update', function (metamaskState) {
store.dispatch(actions.updateMetamaskState(metamaskState))
})
// global metamask api - used by tooling
global.metamask = {
updateCurrentLocale: (code) => {
store.dispatch(actions.updateCurrentLocale(code))
},
setProviderType: (type) => {
store.dispatch(actions.setProviderType(type))
},
setFeatureFlag: (key, value) => {
store.dispatch(actions.setFeatureFlag(key, value))
},
}
// start app
render(
<Root
store={store}
/>,
opts.container,
)
return store
}
/**
* Return a "masked" copy of the given object.
*
* The returned object includes only the properties present in the mask. The
* mask is an object that mirrors the structure of the given object, except
* the only values are `true` or a sub-mask. `true` implies the property
* should be included, and a sub-mask implies the property should be further
* masked according to that sub-mask.
*
* @param {Object} object - The object to mask
* @param {Object<Object|boolean>} mask - The mask to apply to the object
*/
function maskObject (object, mask) {
return Object.keys(object)
.reduce(
(state, key) => {
if (mask[key] === true) {
state[key] = object[key]
} else if (mask[key]) {
state[key] = maskObject(object[key], mask[key])
}
return state
},
{},
)
}
function setupDebuggingHelpers (store) {
window.getCleanAppState = function () {
const state = clone(store.getState())
state.version = global.platform.getVersion()
state.browser = window.navigator.userAgent
return state
}
window.getSentryState = function () {
const fullState = store.getState()
const debugState = maskObject(fullState, SENTRY_STATE)
return {
browser: window.navigator.userAgent,
store: debugState,
version: global.platform.getVersion(),
}
}
}
window.logStateString = function (cb) {
const state = window.getCleanAppState()
global.platform.getPlatformInfo((err, platform) => {
if (err) {
return cb(err)
}
state.platform = platform
const stateString = JSON.stringify(state, null, 2)
cb(null, stateString)
})
}
window.logState = function (toClipboard) {
return window.logStateString((err, result) => {
if (err) {
console.error(err.message)
} else if (toClipboard) {
copyToClipboard(result)
console.log('State log copied')
} else {
console.log(result)
}
})
}