diff --git a/src/index.ts b/src/index.ts index 2141c3e..a821ee0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,7 +9,7 @@ interface MetaMaskEthereumProvider { } interface Window { - ethereum?: MetaMaskEthereumProvider; + ethereum?: MetaMaskEthereumProvider & { providers?: MetaMaskEthereumProvider[] }; } export = detectEthereumProvider; @@ -67,9 +67,9 @@ function detectEthereumProvider({ window.removeEventListener('ethereum#initialized', handleEthereum); - const { ethereum } = window as Window; + const ethereum = getEthereum(mustBeMetaMask); - if (ethereum && (!mustBeMetaMask || ethereum.isMetaMask)) { + if (ethereum) { resolve(ethereum as unknown as T); } else { @@ -95,3 +95,26 @@ function detectEthereumProvider({ } } } + +function getEthereum(mustBeMetaMask: boolean) { + const { ethereum } = window as Window; + if (!ethereum) { + return undefined; + } + // The `providers` field is populated when CoinBase Wallet extension is also installed + // The expected object is an array of providers, the MetaMask provider is inside + // See https://docs.cloud.coinbase.com/wallet-sdk/docs/injected-provider-guidance for more information + if (Array.isArray(ethereum.providers)) { + if (mustBeMetaMask) { + return ethereum.providers.find((p) => p.isMetaMask); + } + return ethereum.providers[0]; + } + if (!mustBeMetaMask) { + return ethereum; + } + if (!ethereum.isMetaMask) { + return undefined; + } + return ethereum; +} diff --git a/test/spec.js b/test/spec.js index 7489cf9..e502191 100644 --- a/test/spec.js +++ b/test/spec.js @@ -24,6 +24,16 @@ const providerWithMetaMask = { const providerNoMetaMask = {} const noProvider = null +const manyProvidersWithMetaMask = { + providers: [{ isMetaMask: true }], +} +const manyProvidersWithoutMetaMask = { + providers: [{}], +} +const manyProvidersNoProvider = { + providers: [], +} + test('detectProvider: defaults with ethereum already set', async function (t) { mockGlobalProps(providerNoMetaMask) @@ -36,6 +46,18 @@ test('detectProvider: defaults with ethereum already set', async function (t) { t.end() }) +test('detectProvider: defaults with ethereum already set in `providers` array field', async function (t) { + + mockGlobalProps(manyProvidersWithoutMetaMask) + + const provider = await detectProvider() + + t.deepEquals({}, provider, 'resolve with expected provider') + t.ok(window.addEventListener.notCalled, 'addEventListener should not have been called') + t.ok(window.removeEventListener.calledOnce, 'removeEventListener called once') + t.end() +}) + test('detectProvider: mustBeMetamask with ethereum already set', async function (t) { mockGlobalProps(providerWithMetaMask) @@ -48,6 +70,18 @@ test('detectProvider: mustBeMetamask with ethereum already set', async function t.end() }) +test('detectProvider: mustBeMetamask with ethereum already set in `providers` array field', async function (t) { + + mockGlobalProps(manyProvidersWithMetaMask) + + const provider = await detectProvider() + + t.ok(provider.isMetaMask, 'should have resolved expected provider object') + t.ok(window.addEventListener.notCalled, 'addEventListener should not have been called') + t.ok(window.removeEventListener.calledOnce, 'removeEventListener called once') + t.end() +}) + test('detectProvider: mustBeMetamask with non-MetaMask ethereum already set', async function (t) { mockGlobalProps(providerNoMetaMask) @@ -59,6 +93,17 @@ test('detectProvider: mustBeMetamask with non-MetaMask ethereum already set', as t.end() }) +test('detectProvider: mustBeMetamask with non-MetaMask ethereum already set in `providers` array field', async function (t) { + + mockGlobalProps(manyProvidersWithoutMetaMask) + + const result = await detectProvider({ timeout: 1, mustBeMetaMask: true }) + t.equal(result, null, 'promise should have resolved null') + t.ok(window.addEventListener.notCalled, 'addEventListener should not have been called') + t.ok(window.removeEventListener.calledOnce, 'removeEventListener called once') + t.end() +}) + test('detectProvider: ethereum set on ethereum#initialized', async function (t) { mockGlobalProps(noProvider) @@ -119,6 +164,18 @@ test('detectProvider: ethereum never set', async function (t) { t.end() }) +test('detectProvider: ethereum never set with `providers` array field', async function (t) { + + mockGlobalProps(manyProvidersNoProvider) + + const result = await detectProvider({ timeout: 1 }) + t.equal(result, null, 'promise should have resolved null') + t.ok(window.addEventListener.notCalled, 'addEventListener should have been called once') + t.ok(window.removeEventListener.calledOnce, 'removeEventListener should have been called once') + t.ok(console.error.calledOnce, 'console.error should have been called once') + t.end() +}) + test('detectProvider: ethereum never set (silent mode)', async function (t) { mockGlobalProps(noProvider)