@colony/purser-metamask
Version:
A javascript library to interact with a Metamask based Ethereum wallet
174 lines (164 loc) • 5.28 kB
Flow
/* @flow */
import { helpers as messages } from './messages';
import type {
MetamaskInpageProviderType,
MetamaskStateEventsObserverType,
} from './flowtypes';
/*
* @TODO Add isModern() helper method to detect the new version of Metamask
*/
/**
* Detect the Metmask Extensaion
*
* @method detect
*
* @return {boolean} If it's available it will return true, otherwise it will throw
*/
export const detect = async (): Promise<boolean> => {
/*
* Modern Metamask Version
*/
if (global.ethereum) {
/*
* @NOTE This is a temporary failsafe check, since Metmask is running an
* intermediate version which, while it contains most of the `ethereum`
* global object, it doesn't contain this helper method
*
* @TODO Remove legacy metmask object availability check
* After an adequate amount of time has passed
*/
if (global.ethereum.isUnlocked && !(await global.ethereum.isUnlocked())) {
throw new Error(messages.isLocked);
}
/*
* @NOTE This is a temporary failsafe check, since Metmask is running an
* intermediate version which, while it contains most of the `ethereum`
* global object, it doesn't contain this helper method
*
* @TODO Remove legacy metmask object availability check
* After an adequate amount of time has passed
*/
if (global.ethereum.isEnabled && !(await global.ethereum.isEnabled())) {
throw new Error(messages.notEnabled);
}
/*
* @NOTE If the `isUnlocked` and the `isEnabled` methods are not available
* it means we are running the pre-release version of Metamask, just prior
* to the EIP-1102 update, so we just ignore those checks
*/
return true;
}
/*
* Legacy Metamask Version
*/
if (!global.ethereum && global.web3) {
if (
!global.web3.currentProvider ||
!global.web3.currentProvider.publicConfigStore
) {
throw new Error(messages.noInpageProvider);
}
/* eslint-disable-next-line no-underscore-dangle */
if (!global.web3.currentProvider.publicConfigStore._state) {
throw new Error(messages.noProviderState);
}
/* eslint-disable-next-line no-underscore-dangle */
if (!global.web3.currentProvider.publicConfigStore._state.selectedAddress) {
throw new Error(messages.notEnabled);
}
return true;
}
throw new Error(messages.noExtension);
};
/**
* Helper method that wraps a method passed as an argument and first checks
* for Metamask's availablity before calling it.
*
* This is basically a wrapper, so that we can cut down on code repetition, since
* this pattern repeats itself every time we try to access the in-page proxy.
*
* @method methodCaller
*
* @param {Function} callback The method to call, if Metamask is available
* @param {string} errorMessage Optional error message to show to use
* (in case Metamask is not available)
*
* @return {any} It returns the result of the callback execution
*/
export const methodCaller = async (
callback: () => any,
errorMessage: string = '',
): Promise<any> => {
try {
/*
* Detect if the Metamask injected proxy is (still) available
*
* We need this little go-around trick to mock just one export of
* the module, while leaving the rest of the module intact so we can test it
*
* See: https://github.com/facebook/jest/issues/936
*/
/* eslint-disable-next-line no-use-before-define */
await metamaskHelpers.detect();
return callback();
} catch (caughtError) {
throw new Error(
`${errorMessage}${errorMessage ? ' ' : ''}Error: ${caughtError.message}`,
);
}
};
/**
* If the Metamask injected instance is available, get the in-page provider
*
* @method getInpageProvider
*
* @return {Object} The `MetamaskInpageProvider` object instance
*/
export const getInpageProvider = (): MetamaskInpageProviderType => {
/*
* We need this little go-around trick to mock just one export of
* the module, while leaving the rest of the module intact so we can test it
*
* See: https://github.com/facebook/jest/issues/936
*/
if (global.ethereum) {
return global.ethereum;
}
return global.web3.currentProvider;
};
/**
* Add a new observer method to Metamask's state update events
*
* @method setStateEventObserver
*
* @param {Function} observer Function to add the state events update array
*
* @return {number} the length of the state events update array
*/
export const setStateEventObserver = (
observer: MetamaskStateEventsObserverType,
): void => {
const {
publicConfigStore: { _events: stateEvents },
}: MetamaskInpageProviderType =
/*
* We need this little go-around trick to mock just one export of
* the module, while leaving the rest of the module intact so we can test it
*
* See: https://github.com/facebook/jest/issues/936
*/
/* eslint-disable-next-line no-use-before-define */
metamaskHelpers.getInpageProvider();
return stateEvents.update.push(observer);
};
/*
* This default export is only here to help us with testing, otherwise
* it wound't be needed
*/
const metamaskHelpers: Object = {
detect,
methodCaller,
getInpageProvider,
setStateEventObserver,
};
export default metamaskHelpers;