mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-25 20:02:58 +01:00
20e16d41be
* Improve Sentry state pre-initialization Previously the masked state snapshot sent to Sentry would be blank for errors that occured during initialization. Instead we'll now include some basic information in all cases, and a masked copy of the persisted state if it happens after the first time the persisted state is read. * Add test * Fix crash when persisted state not yet fetched * Add descriptions for initial state hooks * Update comments to reflect recent changes * Re-order imports to follow conventions * Move initial state hooks back to module-level The initial state hooks are now setup at the top-level of their module. This ensures that they're setup prior to later imports. Calling a function to setup these hooks in the entrypoint module wouldn't accomplish this even if it was run "before" the imports because ES6 imports always get hoisted to the top of the file. The `localStore` instance wasn't available statically, so a new state hook was introduced for retrieving the most recent retrieved persisted state. * Fix error e2e tests
268 lines
7.7 KiB
JavaScript
268 lines
7.7 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.getSentryAppState = function () {
|
|
const reduxState = store.getState();
|
|
return maskObject(reduxState, SENTRY_UI_STATE);
|
|
};
|
|
}
|
|
|
|
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);
|
|
}
|
|
});
|
|
};
|