1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 01:47:00 +01:00
metamask-extension/ui/index.js
Mark Stacey 4e93b86116
Split Sentry mask into UI and background masks (#20426)
The state mask used to anonymize the Sentry state snapshots has been
split into UI and background masks. This was done to simplify later
refactors. There should be no functional changes.
2023-08-16 14:40:44 -02:30

273 lines
7.8 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 browser from 'webextension-polyfill';
import { getEnvironmentType } from '../app/scripts/lib/util';
import { AlertTypes } from '../shared/constants/alerts';
import { maskObject } from '../shared/modules/object.utils';
import { SENTRY_UI_STATE } from '../app/scripts/lib/setupSentry';
import { ENVIRONMENT_TYPE_POPUP } from '../shared/constants/app';
import switchDirection from '../shared/lib/switch-direction';
import { setupLocale } from '../shared/lib/error-utils';
import * as actions from './store/actions';
import configureStore from './store/store';
import {
getPermittedAccountsForCurrentTab,
getSelectedAddress,
} from './selectors';
import { ALERT_STATE } from './ducks/alerts';
import {
getUnconnectedAccountAlertEnabledness,
getUnconnectedAccountAlertShown,
} from './ducks/metamask/metamask';
import Root from './pages';
import txHelper from './helpers/utils/tx-helper';
import { _setBackgroundConnection } from './store/action-queue';
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn');
let reduxStore;
/**
* Method to update backgroundConnection object use by UI
*
* @param backgroundConnection - connection object to background
*/
export const updateBackgroundConnection = (backgroundConnection) => {
_setBackgroundConnection(backgroundConnection);
backgroundConnection.onNotification((data) => {
if (data.method === 'sendUpdate') {
reduxStore.dispatch(actions.updateMetamaskState(data.params[0]));
} else {
throw new Error(
`Internal JSON-RPC Notification Not Handled:\n\n ${JSON.stringify(
data,
)}`,
);
}
});
};
export default function launchMetamaskUi(opts, cb) {
const { backgroundConnection } = opts;
///: BEGIN:ONLY_INCLUDE_IN(desktop)
let desktopEnabled = false;
backgroundConnection.getDesktopEnabled(function (err, result) {
if (err) {
return;
}
desktopEnabled = result;
});
///: END:ONLY_INCLUDE_IN
// check if we are unlocked first
backgroundConnection.getState(function (err, metamaskState) {
if (err) {
cb(
err,
{
...metamaskState,
///: BEGIN:ONLY_INCLUDE_IN(desktop)
desktopEnabled,
///: END:ONLY_INCLUDE_IN
},
backgroundConnection,
);
return;
}
startApp(metamaskState, backgroundConnection, opts).then((store) => {
setupStateHooks(store);
cb(
null,
store,
///: BEGIN:ONLY_INCLUDE_IN(desktop)
backgroundConnection,
///: END:ONLY_INCLUDE_IN
);
});
});
}
async function startApp(metamaskState, backgroundConnection, opts) {
// parse opts
if (!metamaskState.featureFlags) {
metamaskState.featureFlags = {};
}
const { currentLocaleMessages, enLocaleMessages } = await setupLocale(
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: {
currentLocale: metamaskState.currentLocale,
current: currentLocaleMessages,
en: enLocaleMessages,
},
};
updateBackgroundConnection(backgroundConnection);
if (getEnvironmentType() === ENVIRONMENT_TYPE_POPUP) {
const { origin } = draftInitialState.activeTab;
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[AlertTypes.unconnectedAccount] = {
state: ALERT_STATE.OPEN,
};
actions.setUnconnectedAccountAlertShown(origin);
}
}
const store = configureStore(draftInitialState);
reduxStore = store;
// if unconfirmed txs, start on txConf page
const unapprovedTxsAll = txHelper(
metamaskState.unapprovedTxs,
metamaskState.unapprovedMsgs,
metamaskState.unapprovedPersonalMsgs,
metamaskState.unapprovedDecryptMsgs,
metamaskState.unapprovedEncryptionPublicKeyMsgs,
metamaskState.unapprovedTypedMessages,
metamaskState.networkId,
metamaskState.providerConfig.chainId,
);
const numberOfUnapprovedTx = unapprovedTxsAll.length;
if (numberOfUnapprovedTx > 0) {
store.dispatch(
actions.showConfTxPage({
id: unapprovedTxsAll[0].id,
}),
);
}
// 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;
}
/**
* Setup functions on `window.stateHooks`. Some of these support
* application features, and some are just for debugging or testing.
*
* @param {object} store - The Redux store.
*/
function setupStateHooks(store) {
if (process.env.METAMASK_DEBUG || process.env.IN_TEST) {
/**
* The following stateHook is a method intended to throw an error, used in
* our E2E test to ensure that errors are attempted to be sent to sentry.
*
* @param {string} [msg] - The error message to throw, defaults to 'Test Error'
*/
window.stateHooks.throwTestError = async function (msg = 'Test Error') {
const error = new Error(msg);
error.name = 'TestError';
throw error;
};
/**
* The following stateHook is a method intended to throw an error in the
* background, used in our E2E test to ensure that errors are attempted to be
* sent to sentry.
*
* @param {string} [msg] - The error message to throw, defaults to 'Test Error'
*/
window.stateHooks.throwTestBackgroundError = async function (
msg = 'Test Error',
) {
store.dispatch(actions.throwTestBackgroundError(msg));
};
}
window.stateHooks.getCleanAppState = async function () {
const state = clone(store.getState());
state.version = global.platform.getVersion();
state.browser = window.navigator.userAgent;
state.completeTxList = await actions.getTransactions({
filterToCurrentNetwork: false,
});
return state;
};
window.stateHooks.getSentryState = function () {
const reduxState = store.getState();
const maskedReduxState = maskObject(reduxState, SENTRY_UI_STATE);
return {
browser: window.navigator.userAgent,
store: maskedReduxState,
version: global.platform.getVersion(),
};
};
}
window.logStateString = async function (cb) {
const state = await window.stateHooks.getCleanAppState();
browser.runtime
.getPlatformInfo()
.then((platform) => {
state.platform = platform;
const stateString = JSON.stringify(state, null, 2);
cb(null, stateString);
})
.catch((err) => {
cb(err);
});
};
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);
}
});
};