1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-24 19:10:22 +01:00

MV3: add retry logic to actions (#15337)

This commit is contained in:
Jyoti Puri 2022-09-05 20:25:34 +05:30 committed by GitHub
parent ece5901b40
commit 99ed42b3dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 1161 additions and 443 deletions

View File

@ -11,7 +11,7 @@ import MetaMetricsProviderStorybook from './metametrics';
import testData from './test-data.js'; import testData from './test-data.js';
import { Router } from 'react-router-dom'; import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history'; import { createBrowserHistory } from 'history';
import { _setBackgroundConnection } from '../ui/store/actions'; import { _setBackgroundConnection } from '../ui/store/action-queue';
import MetaMaskStorybookTheme from './metamask-storybook-theme'; import MetaMaskStorybookTheme from './metamask-storybook-theme';
import addons from '@storybook/addons'; import addons from '@storybook/addons';

View File

@ -71,6 +71,8 @@ async function start() {
const messageListener = (message) => { const messageListener = (message) => {
if (message?.name === 'CONNECTION_READY') { if (message?.name === 'CONNECTION_READY') {
if (isUIInitialised) { if (isUIInitialised) {
// Currently when service worker is revived we create new streams
// in later version we might try to improve it by reviving same streams.
updateUiStreams(); updateUiStreams();
} else { } else {
initializeUiWithTab(activeTab); initializeUiWithTab(activeTab);

View File

@ -1,5 +1,5 @@
import * as actions from '../../ui/store/actions'; import { _setBackgroundConnection } from '../../ui/store/action-queue';
export const setBackgroundConnection = (backgroundConnection = {}) => { export const setBackgroundConnection = (backgroundConnection = {}) => {
actions._setBackgroundConnection(backgroundConnection); _setBackgroundConnection(backgroundConnection);
}; };

View File

@ -28,6 +28,7 @@ import {
} from './ducks/metamask/metamask'; } from './ducks/metamask/metamask';
import Root from './pages'; import Root from './pages';
import txHelper from './helpers/utils/tx-helper'; import txHelper from './helpers/utils/tx-helper';
import { _setBackgroundConnection } from './store/action-queue';
log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn'); log.setLevel(global.METAMASK_DEBUG ? 'debug' : 'warn');
@ -39,7 +40,7 @@ let reduxStore;
* @param backgroundConnection - connection object to background * @param backgroundConnection - connection object to background
*/ */
export const updateBackgroundConnection = (backgroundConnection) => { export const updateBackgroundConnection = (backgroundConnection) => {
actions._setBackgroundConnection(backgroundConnection); _setBackgroundConnection(backgroundConnection);
backgroundConnection.onNotification((data) => { backgroundConnection.onNotification((data) => {
if (data.method === 'sendUpdate') { if (data.method === 'sendUpdate') {
reduxStore.dispatch(actions.updateMetamaskState(data.params[0])); reduxStore.dispatch(actions.updateMetamaskState(data.params[0]));

View File

@ -0,0 +1,200 @@
import pify from 'pify';
import { isManifestV3 } from '../../../shared/modules/mv3.utils';
// // A simplified pify maybe?
// function pify(apiObject) {
// return Object.keys(apiObject).reduce((promisifiedAPI, key) => {
// if (apiObject[key].apply) { // depending on our browser support we might use a nicer check for functions here
// promisifiedAPI[key] = function (...args) {
// return new Promise((resolve, reject) => {
// return apiObject[key](
// ...args,
// (err, result) => {
// if (err) {
// reject(err);
// } else {
// resolve(result);
// }
// },
// );
// });
// };
// }
// return promisifiedAPI;
// }, {});
// }
let background = null;
let promisifiedBackground = null;
const actionRetryQueue = [];
function failQueue() {
actionRetryQueue.forEach(({ reject }) =>
reject(
Error('Background operation cancelled while waiting for connection.'),
),
);
}
/**
* Drops the entire actions queue. Rejects all actions in the queue unless silently==true
* Does not affect the single action that is currently being processed.
*
* @param {boolean} [silently]
*/
export function dropQueue(silently) {
if (!silently) {
failQueue();
}
actionRetryQueue.length = 0;
}
// add action to queue
const executeActionOrAddToRetryQueue = (item) => {
if (actionRetryQueue.some((act) => act.actionId === item.actionId)) {
return;
}
if (background.connectionStream.readable) {
executeAction({
action: item,
disconnectSideeffect: () => actionRetryQueue.push(item),
});
} else {
actionRetryQueue.push(item);
}
};
/**
* Promise-style call to background method
* In MV2: invokes promisifiedBackground method directly.
* In MV3: action is added to retry queue, along with resolve handler to be executed on completion,
* the queue is then immediately processed if background connection is available.
* On completion (successful or error) the action is removed from the retry queue.
*
* @param {string} method - name of the background method
* @param {Array} [args] - arguments to that method, if any
* @param {any} [actionId] - if an action with the === same id is submitted, it'll be ignored if already in queue waiting for a retry.
* @returns {Promise}
*/
export function submitRequestToBackground(
method,
args = [],
actionId = Date.now() + Math.random(), // current date is not guaranteed to be unique
) {
if (isManifestV3) {
return new Promise((resolve, reject) => {
executeActionOrAddToRetryQueue({
actionId,
request: { method, args },
resolve,
reject,
});
});
}
return promisifiedBackground[method](...args);
}
/**
* Callback-style call to background method
* In MV2: invokes promisifiedBackground method directly.
* In MV3: action is added to retry queue, along with resolve handler to be executed on completion,
* the queue is then immediately processed if background connection is available.
* On completion (successful or error) the action is removed from the retry queue.
*
* @param {string} method - name of the background method
* @param {Array} [args] - arguments to that method, if any
* @param callback - Node style (error, result) callback for finishing the operation
* @param {any} [actionId] - if an action with the === same id is submitted, it'll be ignored if already in queue.
*/
export const callBackgroundMethod = (
method,
args = [],
callback,
actionId = Date.now() + Math.random(), // current date is not guaranteed to be unique
) => {
if (isManifestV3) {
const resolve = (value) => callback(null, value);
const reject = (err) => callback(err);
executeActionOrAddToRetryQueue({
actionId,
request: { method, args },
resolve,
reject,
});
} else {
background[method](...args, callback);
}
};
async function executeAction({ action, disconnectSideeffect }) {
const {
request: { method, args },
resolve,
reject,
} = action;
try {
resolve(await promisifiedBackground[method](...args));
} catch (err) {
if (
background.DisconnectError && // necessary to not break compatibility with background stubs or non-default implementations
err instanceof background.DisconnectError
) {
disconnectSideeffect(action);
} else {
reject(err);
}
}
}
let processingQueue = false;
// Clears list of pending action in actionRetryQueue
// The results of background calls are wired up to the original promises that's been returned
// The first method on the queue gets called synchronously to make testing and reasoning about
// a single request to an open connection easier.
async function processActionRetryQueue() {
if (processingQueue) {
return;
}
processingQueue = true;
try {
while (
background.connectionStream.readable &&
actionRetryQueue.length > 0
) {
// If background disconnects and fails the action, the next one will not be taken off the queue.
// Retrying an action that failed because of connection loss while it was processing is not supported.
const item = actionRetryQueue.shift();
await executeAction({
action: item,
disconnectSideeffect: () => actionRetryQueue.unshift(item),
});
}
} catch (e) {
// error in the queue mechanism itself, the action was malformed
console.error(e);
}
processingQueue = false;
}
/**
* Sets/replaces the background connection reference
* Under MV3 it also triggers queue processing if the new background is connected
*
* @param {*} backgroundConnection
*/
export async function _setBackgroundConnection(backgroundConnection) {
background = backgroundConnection;
promisifiedBackground = pify(background);
if (isManifestV3) {
if (processingQueue) {
console.warn(
'_setBackgroundConnection called while a queue was processing and not disconnected yet',
);
}
// Process all actions collected while connection stream was not available.
processActionRetryQueue();
}
}

View File

@ -0,0 +1,358 @@
import sinon from 'sinon';
import {
dropQueue,
callBackgroundMethod,
submitRequestToBackground,
_setBackgroundConnection,
} from '.';
// This file tests only MV3 queue scenario
// MV2 tests are already covered by '../actions.test.js'
jest.mock('../../../shared/modules/mv3.utils', () => {
return {
isManifestV3: () => true,
};
});
describe('ActionQueue', () => {
afterEach(() => {
sinon.restore();
dropQueue(true);
});
describe('dropQueue', () => {
it('rejects all pending actions by default', async () => {
const background = {
connectionStream: {
readable: false,
},
backgroundFunction: sinon.stub().yields(),
};
_setBackgroundConnection(background);
const result = submitRequestToBackground('backgroundFunction');
dropQueue();
await expect(result).rejects.toThrow(
'Background operation cancelled while waiting for connection.',
);
expect(background.backgroundFunction.called).toStrictEqual(false);
});
});
describe('submitRequestToBackground', () => {
it('calls promisified background method if the stream is connected', async () => {
const background = {
connectionStream: {
readable: true,
},
backgroundFunction1: sinon.stub().yields(),
};
_setBackgroundConnection(background);
submitRequestToBackground('backgroundFunction1');
expect(background.backgroundFunction1.called).toStrictEqual(true);
});
it('does not calls promisified background method if the stream is not connected', async () => {
const background = {
connectionStream: {
readable: false,
},
backgroundFunction2: sinon.stub().yields(),
};
_setBackgroundConnection(background);
submitRequestToBackground('backgroundFunction2');
expect(background.backgroundFunction2.called).toStrictEqual(false);
});
it('calls promisified background method on stream reconnection', async () => {
const background = {
connectionStream: {
readable: false,
},
backgroundFunction3: sinon.stub().yields(),
};
_setBackgroundConnection(background);
const requestPromise = submitRequestToBackground('backgroundFunction3');
background.connectionStream = {
readable: true,
};
_setBackgroundConnection(background);
await requestPromise;
expect(background.backgroundFunction3.calledOnce).toStrictEqual(true);
});
it('resolves if backgroundFunction resolves', async () => {
const background = {
connectionStream: {
readable: true,
},
backgroundFunction4: (cb) => {
return cb(null, 'test');
},
};
_setBackgroundConnection(background);
await expect(
submitRequestToBackground('backgroundFunction4'),
).resolves.toStrictEqual('test');
});
it('rejects if backgroundFunction throws exception', async () => {
expect.assertions(1);
const background = {
connectionStream: {
readable: true,
},
backgroundFunction: () => {
throw Error('test');
},
};
_setBackgroundConnection(background);
await expect(
submitRequestToBackground('backgroundFunction'),
).rejects.toThrow('test');
});
it('calls methods in parallel when connection available', async () => {
const trace = {};
const background = {
connectionStream: {
readable: true,
},
first: (cb) => {
setTimeout(() => {
trace.firstDone = Date.now();
cb(null, 'first');
}, 5);
},
second: (cb) => {
trace.secondStarted = Date.now();
setTimeout(() => cb(null, 'second'), 10);
},
};
_setBackgroundConnection(background);
const scheduled = Promise.all([
submitRequestToBackground('first'),
submitRequestToBackground('second'),
]);
await scheduled;
expect(trace.firstDone).toBeGreaterThan(trace.secondStarted);
});
it('processes the queue sequentially when connection is restored', async () => {
const trace = {};
const background = {
connectionStream: {
readable: false,
},
first: (cb) => {
setTimeout(() => {
trace.firstDone = Date.now();
cb(null, 'first');
}, 5);
},
second: (cb) => {
trace.secondStarted = Date.now();
setTimeout(() => cb(null, 'second'), 10);
},
};
_setBackgroundConnection(background);
const scheduled = Promise.all([
submitRequestToBackground('first'),
submitRequestToBackground('second'),
]);
background.connectionStream.readable = true;
_setBackgroundConnection(background);
await scheduled;
expect(trace.firstDone).toBeLessThanOrEqual(trace.secondStarted);
});
it('ensures actions in queue will not repeat once finished', async () => {
const trace = { calls: 0 };
const background = {
connectionStream: {
readable: false,
},
first: (cb) => {
trace.calls += 1;
setTimeout(() => {
trace.firstDone = Date.now();
cb(null, 'first');
}, 5);
},
second: (cb) => {
trace.calls += 1;
trace.secondStarted = Date.now();
setTimeout(() => cb(null, 'second'), 10);
},
};
_setBackgroundConnection(background);
const scheduled = Promise.all([
submitRequestToBackground('first'),
submitRequestToBackground('second'),
]);
background.connectionStream.readable = true;
_setBackgroundConnection(background);
await scheduled;
_setBackgroundConnection(background); // once all actions finished, this triggers draining the queue again
expect(trace.firstDone).toBeLessThanOrEqual(trace.secondStarted);
expect(trace.calls).toStrictEqual(2);
});
it('stops processng the queue if connection is lost', async () => {
const trace = {};
const background = {
connectionStream: {
readable: false,
},
first: (cb) => {
setTimeout(() => {
trace.firstDone = true;
background.connectionStream.readable = false;
cb(Error('lost connection'));
}, 5);
},
second: sinon.stub().yields(),
};
_setBackgroundConnection(background);
const scheduled = Promise.race([
submitRequestToBackground('first').catch(() => ({})),
submitRequestToBackground('second'),
]);
background.connectionStream.readable = true;
_setBackgroundConnection(background);
await scheduled;
await Promise.resolve('one more tick'); // One asynchronous tick to avoid depending on implementation details
expect(trace.firstDone).toStrictEqual(true);
expect(background.second.called).toStrictEqual(false);
});
// Failing test for a race condition related to how items are removed from queue
it('avoids race conditions', async () => {
const trace = { first: 0, second: 0 };
const flowControl = {};
const background = {
connectionStream: {
readable: false,
},
first: (cb) => {
trace.first += 1;
setTimeout(() => {
flowControl.triggerRaceCondition();
cb(null, 'first');
}, 5);
},
second: (cb) => {
trace.second += 1;
setTimeout(() => cb(null, 'second'), 10);
},
third: sinon.stub().yields(),
};
flowControl.triggerRaceCondition = () => {
flowControl.waitFor = submitRequestToBackground('third');
};
_setBackgroundConnection(background);
const scheduled = Promise.all([
submitRequestToBackground('first'),
submitRequestToBackground('second'),
]);
background.connectionStream.readable = true;
_setBackgroundConnection(background);
await scheduled;
await flowControl.waitFor;
expect(trace.first).toStrictEqual(1);
expect(trace.second).toStrictEqual(1);
expect(background.third.calledOnce).toStrictEqual(true);
});
});
describe('callBackgroundMethod', () => {
afterEach(() => {
sinon.restore();
});
it('calls background method if the stream is connected', async () => {
const background = {
connectionStream: {
readable: true,
},
backgroundFunction: sinon.stub().yields(),
};
_setBackgroundConnection(background);
callBackgroundMethod('backgroundFunction', [], () => ({}));
expect(background.backgroundFunction.called).toStrictEqual(true);
});
it('does not call background method if the stream is not connected', async () => {
const background = {
connectionStream: {
readable: false,
},
backgroundFunction: sinon.stub(),
};
_setBackgroundConnection(background);
callBackgroundMethod('backgroundFunction', [], () => ({}));
expect(background.backgroundFunction.called).toStrictEqual(false);
});
it('calls background method on stream reconnection', async () => {
const background = {
connectionStream: {
readable: false,
},
backgroundFunction: sinon.stub().yields(),
};
_setBackgroundConnection(background);
callBackgroundMethod('backgroundFunction', [], () => ({}));
expect(background.backgroundFunction.called).toStrictEqual(false);
background.connectionStream = {
readable: true,
};
_setBackgroundConnection(background);
expect(background.backgroundFunction.calledOnce).toStrictEqual(true);
});
it('resolves if backgroundFunction called resolves', async () => {
const background = {
connectionStream: {
readable: true,
},
backgroundFunction: (cb) => {
return cb(null, 'successViaCallback');
},
};
_setBackgroundConnection(background);
const value = await new Promise((resolve) => {
callBackgroundMethod('backgroundFunction', [], (_err, result) => {
resolve(result);
});
});
expect(value).toStrictEqual('successViaCallback');
});
it('rejects if backgroundFunction called rejects', async () => {
const errorViaCallback = Error('errorViaCallback');
const background = {
connectionStream: {
readable: true,
},
backgroundFunction: (cb) => {
return cb(errorViaCallback);
},
};
_setBackgroundConnection(background);
const value = await new Promise((resolve) => {
callBackgroundMethod('backgroundFunction', [], (err) => {
resolve(err);
});
});
expect(value).toStrictEqual(errorViaCallback);
});
});
});

View File

@ -0,0 +1,66 @@
import sinon from 'sinon';
import PortStream from 'extension-port-stream';
import { setupMultiplex } from '../../../app/scripts/lib/stream-utils';
import metaRPCClientFactory from '../../../app/scripts/lib/metaRPCClientFactory';
import {
dropQueue,
submitRequestToBackground,
_setBackgroundConnection,
} from '.';
jest.mock('../../../shared/modules/mv3.utils', () => {
return {
isManifestV3: () => true,
};
});
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
describe('queue integration test', () => {
afterEach(() => {
dropQueue(true);
});
it('schedules a retry if background method failed because of a disconnect', async () => {
let disconnectListener;
const extensionPort = {
onMessage: {
addListener: sinon.stub(),
},
onDisconnect: {
addListener(cb) {
disconnectListener = cb;
},
},
postMessage: sinon.stub().callsFake(() => {
disconnectListener();
}),
};
const connectionStream = new PortStream(extensionPort);
const mx = setupMultiplex(connectionStream);
const multiplexStream1 = mx.createStream('controller');
const background = metaRPCClientFactory(multiplexStream1);
_setBackgroundConnection(background);
// disconnect will happen on the attempt to send the message
const finished = submitRequestToBackground('backgroundFunction').catch(
(error) => {
// disconnect error should not get propagated, we retry.
// eslint-disable-next-line jest/no-conditional-expect
expect(error).not.toBeInstanceOf(background.DisconnectError);
// eslint-disable-next-line jest/no-conditional-expect
expect(error.message).toContain('cancelled');
},
);
// We want to make sure we disconnect in the middle of processing, so we have to wait for the control flow to reach postMessage
// undetermined number of asynchronous jumps withing the stream implementation leaves no other option
await wait(3);
// we drop the queue because we're expecting the action to have been returned to the queue and this is the simplest way to check that
dropQueue();
expect(extensionPort.postMessage.calledOnce).toStrictEqual(true);
await finished;
});
});

File diff suppressed because it is too large Load Diff

View File

@ -7,6 +7,7 @@ import { TRANSACTION_STATUSES } from '../../shared/constants/transaction';
import { DEVICE_NAMES } from '../../shared/constants/hardware-wallets'; import { DEVICE_NAMES } from '../../shared/constants/hardware-wallets';
import { GAS_LIMITS } from '../../shared/constants/gas'; import { GAS_LIMITS } from '../../shared/constants/gas';
import * as actions from './actions'; import * as actions from './actions';
import { _setBackgroundConnection } from './action-queue';
const middleware = [thunk]; const middleware = [thunk];
const defaultState = { const defaultState = {
@ -56,7 +57,7 @@ describe('Actions', () => {
cb(), cb(),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -81,7 +82,7 @@ describe('Actions', () => {
background.submitPassword.callsFake((_, cb) => cb(new Error('error'))); background.submitPassword.callsFake((_, cb) => cb(new Error('error')));
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -111,7 +112,7 @@ describe('Actions', () => {
background.unMarkPasswordForgotten.callsFake((cb) => cb()); background.unMarkPasswordForgotten.callsFake((cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch( await store.dispatch(
actions.createNewVaultAndRestore('password', 'test'), actions.createNewVaultAndRestore('password', 'test'),
@ -125,7 +126,7 @@ describe('Actions', () => {
background.createNewVaultAndRestore.callsFake((_, __, cb) => cb()); background.createNewVaultAndRestore.callsFake((_, __, cb) => cb());
background.unMarkPasswordForgotten.callsFake((cb) => cb()); background.unMarkPasswordForgotten.callsFake((cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -152,7 +153,7 @@ describe('Actions', () => {
cb(new Error('error')), cb(new Error('error')),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -183,7 +184,7 @@ describe('Actions', () => {
cb(null, Array.from(Buffer.from('test').values())), cb(null, Array.from(Buffer.from('test').values())),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.requestRevealSeedWords()); await store.dispatch(actions.requestRevealSeedWords());
expect(verifyPassword.callCount).toStrictEqual(1); expect(verifyPassword.callCount).toStrictEqual(1);
@ -198,7 +199,7 @@ describe('Actions', () => {
cb(new Error('error')); cb(new Error('error'));
}); });
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -244,7 +245,7 @@ describe('Actions', () => {
const removeAccount = background.removeAccount.callsFake((_, cb) => cb()); const removeAccount = background.removeAccount.callsFake((_, cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
'SHOW_LOADING_INDICATION', 'SHOW_LOADING_INDICATION',
@ -271,7 +272,7 @@ describe('Actions', () => {
cb(new Error('error')); cb(new Error('error'));
}); });
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -299,7 +300,7 @@ describe('Actions', () => {
const resetAccount = background.resetAccount.callsFake((cb) => cb()); const resetAccount = background.resetAccount.callsFake((cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -319,7 +320,7 @@ describe('Actions', () => {
cb(new Error('error')); cb(new Error('error'));
}); });
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -348,7 +349,7 @@ describe('Actions', () => {
cb(); cb();
}); });
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch( await store.dispatch(
actions.importNewAccount('Private Key', [ actions.importNewAccount('Private Key', [
@ -365,7 +366,7 @@ describe('Actions', () => {
cb(new Error('error')), cb(new Error('error')),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ {
@ -396,7 +397,7 @@ describe('Actions', () => {
}), }),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.addNewAccount(1)); await store.dispatch(actions.addNewAccount(1));
expect(addNewAccount.callCount).toStrictEqual(1); expect(addNewAccount.callCount).toStrictEqual(1);
@ -409,7 +410,7 @@ describe('Actions', () => {
cb(new Error('error')); cb(new Error('error'));
}); });
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -439,7 +440,7 @@ describe('Actions', () => {
}, },
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch( await store.dispatch(
actions.checkHardwareStatus(DEVICE_NAMES.LEDGER, `m/44'/60'/0'/0`), actions.checkHardwareStatus(DEVICE_NAMES.LEDGER, `m/44'/60'/0'/0`),
@ -454,7 +455,7 @@ describe('Actions', () => {
cb(new Error('error')), cb(new Error('error')),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -480,7 +481,7 @@ describe('Actions', () => {
const forgetDevice = background.forgetDevice.callsFake((_, cb) => cb()); const forgetDevice = background.forgetDevice.callsFake((_, cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.forgetDevice(DEVICE_NAMES.LEDGER)); await store.dispatch(actions.forgetDevice(DEVICE_NAMES.LEDGER));
expect(forgetDevice.callCount).toStrictEqual(1); expect(forgetDevice.callCount).toStrictEqual(1);
@ -491,7 +492,7 @@ describe('Actions', () => {
background.forgetDevice.callsFake((_, cb) => cb(new Error('error'))); background.forgetDevice.callsFake((_, cb) => cb(new Error('error')));
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -521,7 +522,7 @@ describe('Actions', () => {
background.establishLedgerTransportPreference.callsFake((cb) => cb()); background.establishLedgerTransportPreference.callsFake((cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch( await store.dispatch(
actions.connectHardware(DEVICE_NAMES.LEDGER, 0, `m/44'/60'/0'/0`), actions.connectHardware(DEVICE_NAMES.LEDGER, 0, `m/44'/60'/0'/0`),
@ -538,7 +539,7 @@ describe('Actions', () => {
background.establishLedgerTransportPreference.callsFake((cb) => cb()); background.establishLedgerTransportPreference.callsFake((cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ {
@ -569,7 +570,7 @@ describe('Actions', () => {
(_, __, ___, ____, cb) => cb(), (_, __, ___, ____, cb) => cb(),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch( await store.dispatch(
actions.unlockHardwareWalletAccounts( actions.unlockHardwareWalletAccounts(
@ -589,7 +590,7 @@ describe('Actions', () => {
cb(new Error('error')), cb(new Error('error')),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -613,7 +614,7 @@ describe('Actions', () => {
it('calls setCurrentCurrency', async () => { it('calls setCurrentCurrency', async () => {
const store = mockStore(); const store = mockStore();
background.setCurrentCurrency = sinon.stub().callsFake((_, cb) => cb()); background.setCurrentCurrency = sinon.stub().callsFake((_, cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.setCurrentCurrency('jpy')); await store.dispatch(actions.setCurrentCurrency('jpy'));
expect(background.setCurrentCurrency.callCount).toStrictEqual(1); expect(background.setCurrentCurrency.callCount).toStrictEqual(1);
@ -624,7 +625,7 @@ describe('Actions', () => {
background.setCurrentCurrency = sinon background.setCurrentCurrency = sinon
.stub() .stub()
.callsFake((_, cb) => cb(new Error('error'))); .callsFake((_, cb) => cb(new Error('error')));
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -655,7 +656,7 @@ describe('Actions', () => {
cb(null, defaultState.metamask), cb(null, defaultState.metamask),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.signMsg(msgParams)); await store.dispatch(actions.signMsg(msgParams));
expect(signMessage.callCount).toStrictEqual(1); expect(signMessage.callCount).toStrictEqual(1);
@ -666,7 +667,7 @@ describe('Actions', () => {
background.signMessage.callsFake((_, cb) => cb(new Error('error'))); background.signMessage.callsFake((_, cb) => cb(new Error('error')));
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -699,7 +700,7 @@ describe('Actions', () => {
(_, cb) => cb(null, defaultState.metamask), (_, cb) => cb(null, defaultState.metamask),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.signPersonalMsg(msgParams)); await store.dispatch(actions.signPersonalMsg(msgParams));
expect(signPersonalMessage.callCount).toStrictEqual(1); expect(signPersonalMessage.callCount).toStrictEqual(1);
@ -712,7 +713,7 @@ describe('Actions', () => {
cb(new Error('error')); cb(new Error('error'));
}); });
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -780,7 +781,7 @@ describe('Actions', () => {
cb(null, defaultState.metamask), cb(null, defaultState.metamask),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.signTypedMsg(msgParamsV3)); await store.dispatch(actions.signTypedMsg(msgParamsV3));
expect(signTypedMsg.callCount).toStrictEqual(1); expect(signTypedMsg.callCount).toStrictEqual(1);
@ -791,7 +792,7 @@ describe('Actions', () => {
background.signTypedMessage.callsFake((_, cb) => cb(new Error('error'))); background.signTypedMessage.callsFake((_, cb) => cb(new Error('error')));
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -837,7 +838,7 @@ describe('Actions', () => {
getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch(actions.updateTransaction(txData)); await store.dispatch(actions.updateTransaction(txData));
@ -865,7 +866,7 @@ describe('Actions', () => {
), ),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -903,7 +904,7 @@ describe('Actions', () => {
const backgroundSetLocked = background.setLocked.callsFake((cb) => cb()); const backgroundSetLocked = background.setLocked.callsFake((cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.lockMetamask()); await store.dispatch(actions.lockMetamask());
expect(backgroundSetLocked.callCount).toStrictEqual(1); expect(backgroundSetLocked.callCount).toStrictEqual(1);
@ -916,7 +917,7 @@ describe('Actions', () => {
cb(new Error('error')); cb(new Error('error'));
}); });
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -945,7 +946,7 @@ describe('Actions', () => {
setSelectedAddress: setSelectedAddressSpy, setSelectedAddress: setSelectedAddressSpy,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch( await store.dispatch(
actions.setSelectedAddress( actions.setSelectedAddress(
@ -966,7 +967,7 @@ describe('Actions', () => {
setSelectedAddress: setSelectedAddressSpy, setSelectedAddress: setSelectedAddressSpy,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -996,7 +997,7 @@ describe('Actions', () => {
setSelectedAddress: setSelectedAddressSpy, setSelectedAddress: setSelectedAddressSpy,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch(actions.showAccountDetail()); await store.dispatch(actions.showAccountDetail());
expect(setSelectedAddressSpy.callCount).toStrictEqual(1); expect(setSelectedAddressSpy.callCount).toStrictEqual(1);
@ -1016,7 +1017,7 @@ describe('Actions', () => {
setSelectedAddress: setSelectedAddressSpy, setSelectedAddress: setSelectedAddressSpy,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1046,7 +1047,7 @@ describe('Actions', () => {
getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch( await store.dispatch(
actions.addToken({ actions.addToken({
@ -1076,7 +1077,7 @@ describe('Actions', () => {
getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1114,7 +1115,7 @@ describe('Actions', () => {
getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch( await store.dispatch(
actions.ignoreTokens({ tokensToIgnore: '0x0000001' }), actions.ignoreTokens({ tokensToIgnore: '0x0000001' }),
@ -1130,7 +1131,7 @@ describe('Actions', () => {
getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)), getState: sinon.stub().callsFake((cb) => cb(null, baseMockState)),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1164,7 +1165,7 @@ describe('Actions', () => {
setProviderType: setProviderTypeStub, setProviderType: setProviderTypeStub,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch(actions.setProviderType()); await store.dispatch(actions.setProviderType());
expect(setProviderTypeStub.callCount).toStrictEqual(1); expect(setProviderTypeStub.callCount).toStrictEqual(1);
@ -1179,7 +1180,7 @@ describe('Actions', () => {
.callsFake((_, cb) => cb(new Error('error'))), .callsFake((_, cb) => cb(new Error('error'))),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
@ -1200,7 +1201,7 @@ describe('Actions', () => {
background.setCustomRpc.callsFake((_, __, ___, ____, cb) => cb()); background.setCustomRpc.callsFake((_, __, ___, ____, cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.setRpcTarget('http://localhost:8545')); await store.dispatch(actions.setRpcTarget('http://localhost:8545'));
expect(background.setCustomRpc.callCount).toStrictEqual(1); expect(background.setCustomRpc.callCount).toStrictEqual(1);
@ -1213,7 +1214,7 @@ describe('Actions', () => {
cb(new Error('error')), cb(new Error('error')),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' }, { type: 'DISPLAY_WARNING', value: 'Had a problem changing networks!' },
@ -1236,7 +1237,7 @@ describe('Actions', () => {
setAddressBook: setAddressBookStub, setAddressBook: setAddressBookStub,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch(actions.addToAddressBook('0x0000')); await store.dispatch(actions.addToAddressBook('0x0000'));
expect(setAddressBookStub.callCount).toStrictEqual(1); expect(setAddressBookStub.callCount).toStrictEqual(1);
@ -1265,7 +1266,7 @@ describe('Actions', () => {
exportAccount: exportAccountStub, exportAccount: exportAccountStub,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1295,7 +1296,7 @@ describe('Actions', () => {
verifyPassword: verifyPasswordStub, verifyPassword: verifyPasswordStub,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1324,7 +1325,7 @@ describe('Actions', () => {
exportAccount: exportAccountStub, exportAccount: exportAccountStub,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1357,7 +1358,7 @@ describe('Actions', () => {
setAccountLabel: setAccountLabelStub, setAccountLabel: setAccountLabelStub,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch( await store.dispatch(
actions.setAccountLabel( actions.setAccountLabel(
@ -1377,7 +1378,7 @@ describe('Actions', () => {
.callsFake((_, __, cb) => cb(new Error('error'))), .callsFake((_, __, cb) => cb(new Error('error'))),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1412,7 +1413,7 @@ describe('Actions', () => {
setFeatureFlag: setFeatureFlagStub, setFeatureFlag: setFeatureFlagStub,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch(actions.setFeatureFlag()); await store.dispatch(actions.setFeatureFlag());
expect(setFeatureFlagStub.callCount).toStrictEqual(1); expect(setFeatureFlagStub.callCount).toStrictEqual(1);
@ -1427,7 +1428,7 @@ describe('Actions', () => {
.callsFake((_, __, cb) => cb(new Error('error'))), .callsFake((_, __, cb) => cb(new Error('error'))),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1456,7 +1457,7 @@ describe('Actions', () => {
completeOnboarding: completeOnboardingStub, completeOnboarding: completeOnboardingStub,
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch(actions.setCompletedOnboarding()); await store.dispatch(actions.setCompletedOnboarding());
expect(completeOnboardingStub.callCount).toStrictEqual(1); expect(completeOnboardingStub.callCount).toStrictEqual(1);
@ -1471,7 +1472,7 @@ describe('Actions', () => {
.callsFake((cb) => cb(new Error('error'))), .callsFake((cb) => cb(new Error('error'))),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1495,7 +1496,7 @@ describe('Actions', () => {
it('calls setUseBlockie in background', async () => { it('calls setUseBlockie in background', async () => {
const store = mockStore(); const store = mockStore();
const setUseBlockieStub = sinon.stub().callsFake((_, cb) => cb()); const setUseBlockieStub = sinon.stub().callsFake((_, cb) => cb());
actions._setBackgroundConnection({ setUseBlockie: setUseBlockieStub }); _setBackgroundConnection({ setUseBlockie: setUseBlockieStub });
await store.dispatch(actions.setUseBlockie()); await store.dispatch(actions.setUseBlockie());
expect(setUseBlockieStub.callCount).toStrictEqual(1); expect(setUseBlockieStub.callCount).toStrictEqual(1);
@ -1507,7 +1508,7 @@ describe('Actions', () => {
cb(new Error('error')); cb(new Error('error'));
}); });
actions._setBackgroundConnection({ setUseBlockie: setUseBlockieStub }); _setBackgroundConnection({ setUseBlockie: setUseBlockieStub });
const expectedActions = [ const expectedActions = [
{ type: 'SHOW_LOADING_INDICATION', value: undefined }, { type: 'SHOW_LOADING_INDICATION', value: undefined },
@ -1535,7 +1536,7 @@ describe('Actions', () => {
it('calls expected actions', async () => { it('calls expected actions', async () => {
const store = mockStore(); const store = mockStore();
const setCurrentLocaleStub = sinon.stub().callsFake((_, cb) => cb()); const setCurrentLocaleStub = sinon.stub().callsFake((_, cb) => cb());
actions._setBackgroundConnection({ _setBackgroundConnection({
setCurrentLocale: setCurrentLocaleStub, setCurrentLocale: setCurrentLocaleStub,
}); });
@ -1558,7 +1559,7 @@ describe('Actions', () => {
const setCurrentLocaleStub = sinon const setCurrentLocaleStub = sinon
.stub() .stub()
.callsFake((_, cb) => cb(new Error('error'))); .callsFake((_, cb) => cb(new Error('error')));
actions._setBackgroundConnection({ _setBackgroundConnection({
setCurrentLocale: setCurrentLocaleStub, setCurrentLocale: setCurrentLocaleStub,
}); });
@ -1584,7 +1585,7 @@ describe('Actions', () => {
background.markPasswordForgotten.callsFake((cb) => cb()); background.markPasswordForgotten.callsFake((cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
await store.dispatch(actions.markPasswordForgotten()); await store.dispatch(actions.markPasswordForgotten());
@ -1603,7 +1604,7 @@ describe('Actions', () => {
cb(new Error('error')), cb(new Error('error')),
); );
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
const expectedActions = [ const expectedActions = [
{ type: 'HIDE_LOADING_INDICATION' }, { type: 'HIDE_LOADING_INDICATION' },
@ -1628,7 +1629,7 @@ describe('Actions', () => {
background.unMarkPasswordForgotten.callsFake((cb) => cb()); background.unMarkPasswordForgotten.callsFake((cb) => cb());
actions._setBackgroundConnection(background); _setBackgroundConnection(background);
store.dispatch(actions.unMarkPasswordForgotten()); store.dispatch(actions.unMarkPasswordForgotten());
@ -1687,7 +1688,7 @@ describe('Actions', () => {
), ),
}); });
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
const txId = 1457634084250832; const txId = 1457634084250832;
@ -1742,7 +1743,7 @@ describe('Actions', () => {
}, },
]; ];
actions._setBackgroundConnection(background.getApi()); _setBackgroundConnection(background.getApi());
await store.dispatch(actions.cancelMsgs(msgsList)); await store.dispatch(actions.cancelMsgs(msgsList));
const resultantActions = store.getActions(); const resultantActions = store.getActions();