From fd6a6df716df2ae4605752e27f71ce84ff9475ac Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Mon, 29 Jul 2024 15:19:00 +0200 Subject: [PATCH 1/9] feat: chainagnostic provider, coreprovider test cases --- .../providers/ChainAgnosticProvider.test.ts | 132 ++++ .../providers/ChainAgnosticProvider.ts | 87 +++ src/background/providers/CoreProvider.test.ts | 615 ++++++++++++++---- src/background/providers/CoreProvider.ts | 94 ++- .../providers/initializeInpageProvider.ts | 41 +- src/background/providers/models.ts | 7 + 6 files changed, 786 insertions(+), 190 deletions(-) create mode 100644 src/background/providers/ChainAgnosticProvider.test.ts create mode 100644 src/background/providers/ChainAgnosticProvider.ts diff --git a/src/background/providers/ChainAgnosticProvider.test.ts b/src/background/providers/ChainAgnosticProvider.test.ts new file mode 100644 index 000000000..b2723a7f3 --- /dev/null +++ b/src/background/providers/ChainAgnosticProvider.test.ts @@ -0,0 +1,132 @@ +import { ethErrors } from 'eth-rpc-errors'; +import AutoPairingPostMessageConnection from '../utils/messaging/AutoPairingPostMessageConnection'; +import { ChainAgnostinProvider } from './ChainAgnosticProvider'; + +jest.mock('./utils/onDomReady'); +jest.mock('../utils/messaging/AutoPairingPostMessageConnection', () => { + const mocks = { + connect: jest.fn().mockResolvedValue(undefined), + on: jest.fn(), + request: jest.fn().mockResolvedValue({}), + }; + return jest.fn().mockReturnValue(mocks); +}); +describe('src/background/providers/ChainAgnosticProvider', () => { + const channelMock = new AutoPairingPostMessageConnection(false); + + describe('initialization', () => { + it('should connect to the backgroundscript', async () => { + new ChainAgnostinProvider(channelMock); + + expect(channelMock.connect).toHaveBeenCalled(); + expect(channelMock.request).not.toHaveBeenCalled(); + }); + it('waits for message channel to be connected', async () => { + const mockedChannel = new AutoPairingPostMessageConnection(false); + + const provider = new ChainAgnostinProvider(channelMock); + expect(mockedChannel.connect).toHaveBeenCalled(); + expect(mockedChannel.request).not.toHaveBeenCalled(); + + await provider.request({ + data: { method: 'some-method', params: [{ param1: 1 }] }, + sessionId: '00000000-0000-0000-0000-000000000000', + chainId: '1', + }); + expect(mockedChannel.request).toHaveBeenCalled(); + }); + }); + + describe('request', () => { + it('should use the rate limits on `eth_requestAccounts` requests', async () => { + const provider = new ChainAgnostinProvider(channelMock); + (channelMock.request as jest.Mock).mockResolvedValue('success'); + + const firstCallCallback = jest.fn(); + const secondCallCallback = jest.fn(); + provider + .request({ + data: { method: 'eth_requestAccounts' }, + } as any) + .then(firstCallCallback) + .catch(firstCallCallback); + provider + .request({ + data: { method: 'eth_requestAccounts' }, + } as any) + .then(secondCallCallback) + .catch(secondCallCallback); + + await new Promise(process.nextTick); + expect(firstCallCallback).toHaveBeenCalledWith('success'); + expect(secondCallCallback).toHaveBeenCalledWith( + ethErrors.rpc.resourceUnavailable( + `Request of type eth_requestAccounts already pending for origin. Please wait.` + ) + ); + }); + it('shoud not use the rate limits on `random_method` requests', async () => { + const provider = new ChainAgnostinProvider(channelMock); + (channelMock.request as jest.Mock).mockResolvedValue('success'); + + const firstCallCallback = jest.fn(); + const secondCallCallback = jest.fn(); + provider + .request({ + data: { method: 'random_method' }, + } as any) + .then(firstCallCallback) + .catch(firstCallCallback); + provider + .request({ + data: { method: 'random_method' }, + } as any) + .then(secondCallCallback) + .catch(secondCallCallback); + + await new Promise(process.nextTick); + expect(firstCallCallback).toHaveBeenCalledWith('success'); + expect(secondCallCallback).toHaveBeenCalledWith('success'); + }); + + it('should call the request of the connection', async () => { + const provider = new ChainAgnostinProvider(channelMock); + (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); + + await provider.request({ + data: { method: 'some-method', params: [{ param1: 1 }] }, + sessionId: '00000000-0000-0000-0000-000000000000', + chainId: '1', + }); + expect(channelMock.request).toHaveBeenCalled(); + }); + describe('CAIP-27', () => { + it('should wrap the incoming request into CAIP-27 envelope and reuses the provided ID', async () => { + const provider = new ChainAgnostinProvider(channelMock); + // response for the actual call + (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); + + provider.request({ + data: { method: 'some-method', params: [{ param1: 1 }] }, + sessionId: '00000000-0000-0000-0000-000000000000', + chainId: '1', + }); + + await new Promise(process.nextTick); + + expect(channelMock.request).toHaveBeenCalledWith({ + jsonrpc: '2.0', + method: 'provider_request', + params: { + scope: 'eip155:1', + sessionId: '00000000-0000-0000-0000-000000000000', + request: { + method: 'some-method', + params: [{ param1: 1 }], + }, + }, + }); + }); + }); + }); +}); diff --git a/src/background/providers/ChainAgnosticProvider.ts b/src/background/providers/ChainAgnosticProvider.ts new file mode 100644 index 000000000..46689c823 --- /dev/null +++ b/src/background/providers/ChainAgnosticProvider.ts @@ -0,0 +1,87 @@ +import EventEmitter from 'events'; +import { + JsonRpcRequest, + JsonRpcRequestPayload, +} from '../connections/dAppConnection/models'; +import { PartialBy } from '../models'; +import { ethErrors, serializeError } from 'eth-rpc-errors'; +import AbstractConnection from '../utils/messaging/AbstractConnection'; +import { ChainId } from '@avalabs/chains-sdk'; +import RequestRatelimiter from './utils/RequestRatelimiter'; + +export class ChainAgnostinProvider extends EventEmitter { + #contentScriptConnection: AbstractConnection; + + #requestRateLimiter = new RequestRatelimiter([ + 'eth_requestAccounts', + 'avalanche_selectWallet', + ]); + + constructor(connection) { + super(); + this.#contentScriptConnection = connection; + this.#init(); + } + + async #init() { + await this.#contentScriptConnection.connect(); + } + + #request = async ({ + data, + sessionId, + chainId, + }: { + data: PartialBy; + sessionId: string; + chainId: string | null; + }) => { + if (!data) { + throw ethErrors.rpc.invalidRequest(); + } + + const result = this.#contentScriptConnection + .request({ + method: 'provider_request', + jsonrpc: '2.0', + params: { + scope: `eip155:${ + chainId ? parseInt(chainId) : ChainId.AVALANCHE_MAINNET_ID + }`, + sessionId, + request: { + params: [], + ...data, + }, + }, + } as JsonRpcRequest) + .catch((err) => { + // If the error is already a JsonRPCErorr do not serialize them. + // eth-rpc-errors always wraps errors if they have an unkown error code + // even if the code is valid like 4902 for unrecognized chain ID. + if (!!err.code && Number.isInteger(err.code) && !!err.message) { + throw err; + } + throw serializeError(err); + }); + return result; + }; + + request = async ({ + data, + sessionId, + chainId, + }: { + data: PartialBy; + sessionId: string; + chainId: string | null; + }) => { + return this.#requestRateLimiter.call(data.method, () => + this.#request({ data, chainId, sessionId }) + ); + }; + + subscribeToMessage = (callback) => { + this.#contentScriptConnection.on('message', callback); + }; +} diff --git a/src/background/providers/CoreProvider.test.ts b/src/background/providers/CoreProvider.test.ts index 0d6d8da13..8fc805cea 100644 --- a/src/background/providers/CoreProvider.test.ts +++ b/src/background/providers/CoreProvider.test.ts @@ -3,6 +3,7 @@ import { CoreProvider } from './CoreProvider'; import onDomReady from './utils/onDomReady'; import { DAppProviderRequest } from '../connections/dAppConnection/models'; import AutoPairingPostMessageConnection from '../utils/messaging/AutoPairingPostMessageConnection'; +import { EventNames } from './models'; jest.mock('../utils/messaging/AutoPairingPostMessageConnection', () => { const mocks = { @@ -17,13 +18,12 @@ jest.mock('./utils/onDomReady'); const matchingPayload = (payload) => expect.objectContaining({ - params: expect.objectContaining({ - request: expect.objectContaining(payload), - }), + data: expect.objectContaining(payload), }); describe('src/background/providers/CoreProvider', () => { const channelMock = new AutoPairingPostMessageConnection(false); + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); beforeEach(() => { jest.mocked(channelMock.connect).mockResolvedValueOnce(undefined); @@ -53,58 +53,22 @@ describe('src/background/providers/CoreProvider', () => { }); }); - describe('CAIP-27', () => { - let provider; - - beforeEach(async () => { - provider = new CoreProvider({ connection: channelMock }); - - // wait for init to finish - await new Promise(process.nextTick); - - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce({}); - - // domReady to allow requests through - (onDomReady as jest.Mock).mock.calls[0][0](); - - await new Promise(process.nextTick); - }); - - it('wraps incoming requests into CAIP-27 envelope and reuses the provided ID', async () => { - // response for the actual call - (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); - - provider.send( - { - method: 'some-method', - params: [{ param1: 1 }], - }, - jest.fn() - ); - - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledWith({ - jsonrpc: '2.0', - method: 'provider_request', - params: { - scope: 'eip155:1', - sessionId: '00000000-0000-0000-0000-000000000000', - request: { - method: 'some-method', - params: [{ param1: 1 }], - }, - }, - }); - }); - }); - describe('EIP-1193', () => { describe('request', () => { it('collects pending requests till the dom is ready', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -116,7 +80,8 @@ describe('src/background/providers/CoreProvider', () => { ); // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce({}); + // TODO: invastigate why it has skipped when all the cases are running + //(channelMock.request as jest.Mock).mockResolvedValueOnce({}); // response for 'some-method' (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); const rpcResultCallback = jest.fn(); @@ -146,64 +111,20 @@ describe('src/background/providers/CoreProvider', () => { expect(rpcResultCallback).toHaveBeenCalledWith('success'); }); - it('rate limits `eth_requestAccounts` requests', async () => { + it.skip('always returns JSON RPC-compatible error', async () => { const provider = new CoreProvider({ connection: channelMock }); - - // wait for init to finish - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(1); - expect(channelMock.request).toHaveBeenCalledWith( - matchingPayload({ - method: DAppProviderRequest.INIT_DAPP_STATE, - }) - ); - - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce({}); - // response for 'eth_requestAccounts' - (channelMock.request as jest.Mock).mockResolvedValue('success'); - const firstCallCallback = jest.fn(); - const secondCallCallback = jest.fn(); - provider - .request({ - method: 'eth_requestAccounts', - }) - .then(firstCallCallback) - .catch(firstCallCallback); - provider - .request({ - method: 'eth_requestAccounts', - }) - .then(secondCallCallback) - .catch(secondCallCallback); - await new Promise(process.nextTick); - - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); - expect(channelMock.request).toHaveBeenCalledWith( - matchingPayload({ - method: 'eth_requestAccounts', - }) - ); - - expect(firstCallCallback).toHaveBeenCalledWith('success'); - expect(secondCallCallback).toHaveBeenCalledWith( - ethErrors.rpc.resourceUnavailable( - `Request of type eth_requestAccounts already pending for origin. Please wait.` - ) - ); - }); - - it('always returns JSON RPC-compatible error', async () => { - const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -253,7 +174,18 @@ describe('src/background/providers/CoreProvider', () => { it('does not double wraps JSON RPC errors', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -308,6 +240,18 @@ describe('src/background/providers/CoreProvider', () => { describe(`connect`, () => { it('emits `connect` when chainId first set', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const connectSubscription = jest.fn(); provider.addListener('connect', connectSubscription); @@ -334,6 +278,18 @@ describe('src/background/providers/CoreProvider', () => { accounts: ['0x00000'], }); const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const connectSubscription = jest.fn(); provider.addListener('connect', connectSubscription); @@ -361,6 +317,18 @@ describe('src/background/providers/CoreProvider', () => { it('emits connect on re-connect after disconnected', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const connectSubscription = jest.fn(); const disconnectSubscription = jest.fn(); provider.addListener('connect', connectSubscription); @@ -396,6 +364,18 @@ describe('src/background/providers/CoreProvider', () => { describe('disconnect', () => { it('emits disconnect event with error', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const disconnectSubscription = jest.fn(); provider.addListener('disconnect', disconnectSubscription); @@ -428,6 +408,18 @@ describe('src/background/providers/CoreProvider', () => { describe('chainChanged', () => { it('does not emit `chainChanged` on initialization', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const chainChangedSubscription = jest.fn(); provider.addListener('chainChanged', chainChangedSubscription); @@ -446,6 +438,18 @@ describe('src/background/providers/CoreProvider', () => { accounts: ['0x00000'], }); const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const chainChangedSubscription = jest.fn(); provider.addListener('chainChanged', chainChangedSubscription); @@ -454,7 +458,15 @@ describe('src/background/providers/CoreProvider', () => { expect(chainChangedSubscription).not.toHaveBeenCalled(); - (channelMock.on as jest.Mock).mock.calls[0][1]({ + console.log( + '(channelMock.req as jest.Mock).mock.: ', + (channelMock.request as jest.Mock).mock + ); + console.log( + '(channelMock.on as jest.Mock).mock.: ', + (channelMock.on as jest.Mock).mock + ); + (channelMock.on as jest.Mock).mock.calls[0]?.[1]({ method: 'chainChanged', params: { chainId: '0x1', networkVersion: '1' }, }); @@ -464,6 +476,18 @@ describe('src/background/providers/CoreProvider', () => { it('does not emit `chainChanged` when chain is set to the same value', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const chainChangedSubscription = jest.fn(); provider.addListener('chainChanged', chainChangedSubscription); @@ -487,6 +511,18 @@ describe('src/background/providers/CoreProvider', () => { it('emits `chainChanged` when chain is set to new value', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const chainChangedSubscription = jest.fn(); provider.addListener('chainChanged', chainChangedSubscription); @@ -514,6 +550,18 @@ describe('src/background/providers/CoreProvider', () => { describe('accountsChanged', () => { it('emits `accountsChanged` on initialization', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const accountsChangedSubscription = jest.fn(); provider.addListener('accountsChanged', accountsChangedSubscription); @@ -535,6 +583,18 @@ describe('src/background/providers/CoreProvider', () => { accounts: undefined, }); const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const accountsChangedSubscription = jest.fn(); provider.addListener('accountsChanged', accountsChangedSubscription); @@ -547,6 +607,18 @@ describe('src/background/providers/CoreProvider', () => { it('does not emit `accountsChanged` when account is set to the same value', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const accountsChangedSubscription = jest.fn(); provider.addListener('accountsChanged', accountsChangedSubscription); @@ -565,6 +637,18 @@ describe('src/background/providers/CoreProvider', () => { it('emits `accountsChanged` when account is set to new value', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const accountsChangedSubscription = jest.fn(); provider.addListener('accountsChanged', accountsChangedSubscription); @@ -590,7 +674,18 @@ describe('src/background/providers/CoreProvider', () => { describe('sendAsync', () => { it('collects pending requests till the dom is ready', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -629,9 +724,20 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it('rate limits `eth_requestAccounts` requests', async () => { + it.skip('rate limits `eth_requestAccounts` requests', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -688,7 +794,18 @@ describe('src/background/providers/CoreProvider', () => { it('supports batched requets', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -741,7 +858,18 @@ describe('src/background/providers/CoreProvider', () => { describe('send', () => { it('collects pending requests till the dom is ready', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -780,9 +908,20 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it('rate limits `eth_requestAccounts` requests', async () => { + it.skip('rate limits `eth_requestAccounts` requests', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -839,7 +978,18 @@ describe('src/background/providers/CoreProvider', () => { it('supports batched requets', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -890,7 +1040,18 @@ describe('src/background/providers/CoreProvider', () => { it('supports method as the only param', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -927,7 +1088,18 @@ describe('src/background/providers/CoreProvider', () => { it('supports method with params', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -964,7 +1136,18 @@ describe('src/background/providers/CoreProvider', () => { it('returns eth_accounts response syncronously', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -980,7 +1163,18 @@ describe('src/background/providers/CoreProvider', () => { it('returns eth_coinbase response syncronously', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -996,7 +1190,18 @@ describe('src/background/providers/CoreProvider', () => { it('throws error if method not supported syncronously', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -1021,7 +1226,18 @@ describe('src/background/providers/CoreProvider', () => { describe('enable', () => { it('collects pending requests till the dom is ready', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -1050,9 +1266,20 @@ describe('src/background/providers/CoreProvider', () => { expect(rpcResultCallback).toHaveBeenCalledWith(['0x0000']); }); - it('rate limits enable calls', async () => { + it.skip('rate limits enable calls', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -1092,7 +1319,18 @@ describe('src/background/providers/CoreProvider', () => { describe('net_version', () => { it('supports net_version call', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); // wait for init to finish await new Promise(process.nextTick); @@ -1125,6 +1363,18 @@ describe('src/background/providers/CoreProvider', () => { describe('close event', () => { it('emits close event with error', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const closeSubscription = jest.fn(); provider.on('close', closeSubscription); @@ -1157,6 +1407,18 @@ describe('src/background/providers/CoreProvider', () => { describe('networkChanged event', () => { it('does not emit `networkChanged` on initialization', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const networkChangedSubscription = jest.fn(); provider.addListener('networkChanged', networkChangedSubscription); @@ -1175,6 +1437,18 @@ describe('src/background/providers/CoreProvider', () => { accounts: ['0x00000'], }); const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const networkChangedSubscription = jest.fn(); provider.addListener('networkChanged', networkChangedSubscription); @@ -1193,6 +1467,18 @@ describe('src/background/providers/CoreProvider', () => { it('does not emit `networkChanged` when chain is set to the same value', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const networkChangedSubscription = jest.fn(); provider.addListener('networkChanged', networkChangedSubscription); @@ -1216,6 +1502,18 @@ describe('src/background/providers/CoreProvider', () => { it('emits `chainChanged` when chain is set to new value', async () => { const provider = new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const networkChangedSubscription = jest.fn(); provider.addListener('networkChanged', networkChangedSubscription); @@ -1243,7 +1541,28 @@ describe('src/background/providers/CoreProvider', () => { }); describe('init', () => { - it('waits for message channel to be connected', async () => { + it('should call the event listener with the right event name', async () => { + new CoreProvider({ connection: channelMock }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + + expect(addEventListenerSpy).toHaveBeenCalledWith( + EventNames.CORE_WALLET_ANNOUNCE_PROVIDER, + expect.any(Function) + ); + }); + it.skip('waits for message channel to be connected', async () => { const mockedChannel = new AutoPairingPostMessageConnection(false); let resolve; const promise = new Promise((res) => { @@ -1253,6 +1572,19 @@ describe('src/background/providers/CoreProvider', () => { jest.mocked(mockedChannel.connect).mockReturnValue(promise); new CoreProvider({ connection: mockedChannel }); + + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); expect(mockedChannel.connect).toHaveBeenCalled(); expect(mockedChannel.request).not.toHaveBeenCalled(); @@ -1272,6 +1604,18 @@ describe('src/background/providers/CoreProvider', () => { accounts: ['0x00000'], }); const provider = new CoreProvider({ connection: mockedChannel }); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); const initializedSubscription = jest.fn(); provider.addListener('_initialized', initializedSubscription); await new Promise(process.nextTick); @@ -1299,7 +1643,18 @@ describe('src/background/providers/CoreProvider', () => { describe('Metamask compatibility', () => { it('supports _metamask.isUnlocked', async () => { const provider = new CoreProvider({ connection: channelMock }); - + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ + detail: { + provider: { + subscribeToMessage: jest.fn((callback) => { + return channelMock.on('message', callback); + }), + request: jest.fn((params) => { + return channelMock.request(params as any); + }), + }, + }, + }); expect(await provider._metamask.isUnlocked()).toBe(false); // wait for init to finish diff --git a/src/background/providers/CoreProvider.ts b/src/background/providers/CoreProvider.ts index 2c9e1b626..6744d35a1 100644 --- a/src/background/providers/CoreProvider.ts +++ b/src/background/providers/CoreProvider.ts @@ -1,12 +1,12 @@ -import { ethErrors, serializeError } from 'eth-rpc-errors'; +import { ethErrors } from 'eth-rpc-errors'; import EventEmitter from 'events'; import { getSiteMetadata } from './utils/getSiteMetadata'; import onDomReady from './utils/onDomReady'; -import RequestRatelimiter from './utils/RequestRatelimiter'; import { - AccountsChangedEventData, - ChainChangedEventData, - UnlockStateChangedEventData, + EventNames, + type AccountsChangedEventData, + type ChainChangedEventData, + type UnlockStateChangedEventData, } from './models'; import { InitializationStep, @@ -14,12 +14,11 @@ import { } from './utils/ProviderReadyPromise'; import { DAppProviderRequest, - JsonRpcRequest, - JsonRpcRequestPayload, + type JsonRpcRequestPayload, } from '../connections/dAppConnection/models'; -import { PartialBy, ProviderInfo } from '../models'; -import AbstractConnection from '../utils/messaging/AbstractConnection'; -import { ChainId } from '@avalabs/chains-sdk'; +import type { PartialBy, ProviderInfo } from '../models'; +import type AbstractConnection from '../utils/messaging/AbstractConnection'; +import { ChainAgnostinProvider } from './ChainAgnosticProvider'; interface ProviderState { accounts: string[] | null; @@ -29,13 +28,10 @@ interface ProviderState { } export class CoreProvider extends EventEmitter { - #contentScriptConnection: AbstractConnection; - #requestRateLimiter = new RequestRatelimiter([ - 'eth_requestAccounts', - 'avalanche_selectWallet', - ]); #providerReadyPromise = new ProviderReadyPromise(); + #chainagnosticProvider?: ChainAgnostinProvider; + readonly info: ProviderInfo = { name: EVM_PROVIDER_INFO_NAME, uuid: EVM_PROVIDER_INFO_UUID, @@ -73,7 +69,6 @@ export class CoreProvider extends EventEmitter { }; constructor({ - connection, maxListeners = 100, }: { connection: AbstractConnection; @@ -81,17 +76,32 @@ export class CoreProvider extends EventEmitter { }) { super(); this.setMaxListeners(maxListeners); - this.#contentScriptConnection = connection; - this.#init(); + this.#subscribe(); } + #subscribe() { + window.addEventListener( + EventNames.CORE_WALLET_ANNOUNCE_PROVIDER, + (event) => { + if (this.#chainagnosticProvider) { + return; + } + this.#chainagnosticProvider = (event).detail.provider; + + this.#chainagnosticProvider?.subscribeToMessage( + this.#handleBackgroundMessage + ); + + this.#init(); + } + ); + + window.dispatchEvent(new Event(EventNames.CORE_WALLET_REQUEST_PROVIDER)); + } /** * Initializes provider state, and collects dApp information */ #init = async () => { - await this.#contentScriptConnection.connect(); - this.#contentScriptConnection.on('message', this.#handleBackgroundMessage); - onDomReady(async () => { const domainMetadata = await getSiteMetadata(); @@ -141,7 +151,6 @@ export class CoreProvider extends EventEmitter { InitializationStep.PROVIDER_STATE_LOADED ); } catch (e) { - console.error(e); // the provider will have a partial state, but still should be able to function } finally { this._initialized = true; @@ -154,40 +163,15 @@ export class CoreProvider extends EventEmitter { #request = async ( data: PartialBy ) => { - if (!data) { - throw ethErrors.rpc.invalidRequest(); - } - - return this.#contentScriptConnection - .request({ - method: 'provider_request', - jsonrpc: '2.0', - params: { - scope: `eip155:${ - this.chainId ? parseInt(this.chainId) : ChainId.AVALANCHE_MAINNET_ID - }`, - sessionId: this._sessionId, - request: { - params: [], - ...data, - }, - }, - } as JsonRpcRequest) - .catch((err) => { - // If the error is already a JsonRPCErorr do not serialize them. - // eth-rpc-errors always wraps errors if they have an unkown error code - // even if the code is valid like 4902 for unrecognized chain ID. - if (!!err.code && Number.isInteger(err.code) && !!err.message) { - throw err; - } - throw serializeError(err); - }); + return this.#chainagnosticProvider?.request({ + data, + chainId: this.chainId, + sessionId: this._sessionId, + }); }; #requestInternal = (data) => { - return this.#requestRateLimiter.call(data.method, () => - this.#request(data) - ); + return this.#request(data); }; #getEventHandler = (method: string): ((params: any) => void) => { @@ -216,9 +200,7 @@ export class CoreProvider extends EventEmitter { request = async (data: PartialBy) => { return this.#providerReadyPromise.call(() => { - return this.#requestRateLimiter.call(data.method, () => - this.#request(data) - ); + return this.#request(data); }); }; diff --git a/src/background/providers/initializeInpageProvider.ts b/src/background/providers/initializeInpageProvider.ts index 03964e0f5..f9b8ab93d 100644 --- a/src/background/providers/initializeInpageProvider.ts +++ b/src/background/providers/initializeInpageProvider.ts @@ -1,7 +1,8 @@ -import AbstractConnection from '../utils/messaging/AbstractConnection'; +import type AbstractConnection from '../utils/messaging/AbstractConnection'; +import { ChainAgnostinProvider } from './ChainAgnosticProvider'; import { CoreProvider } from './CoreProvider'; import { createMultiWalletProxy } from './MultiWalletProviderProxy'; -import { EIP6963ProviderDetail } from './models'; +import { EventNames, type EIP6963ProviderDetail } from './models'; /** * Initializes a CoreProvide and assigns it as window.ethereum. @@ -16,6 +17,13 @@ export function initializeProvider( maxListeners = 100, globalObject = window ): CoreProvider { + const chainAgnosticProvider = new Proxy( + new ChainAgnostinProvider(connection), + { + deleteProperty: () => true, + } + ); + const provider = new Proxy(new CoreProvider({ connection, maxListeners }), { // some common libraries, e.g. web3@1.x, mess with our API deleteProperty: () => true, @@ -25,6 +33,7 @@ export function initializeProvider( setAvalancheGlobalProvider(provider, globalObject); setEvmproviders(provider, globalObject); announceWalletProvider(provider, globalObject); + announceChainAgnosticProvider(chainAgnosticProvider, globalObject); return provider; } @@ -119,7 +128,7 @@ function announceWalletProvider( globalObject = window ): void { const announceEvent = new CustomEvent( - 'eip6963:announceProvider', + EventNames.EIP6963_ANNOUNCE_PROVIDER, { detail: Object.freeze({ info: { ...providerInstance.info }, @@ -134,7 +143,31 @@ function announceWalletProvider( // The Wallet listens to the request events which may be // dispatched later and re-dispatches the `EIP6963AnnounceProviderEvent` - globalObject.addEventListener('eip6963:requestProvider', () => { + globalObject.addEventListener(EventNames.EIP6963_REQUEST_PROVIDER, () => { + globalObject.dispatchEvent(announceEvent); + }); +} + +function announceChainAgnosticProvider( + providerInstance: ChainAgnostinProvider, + globalObject = window +): void { + const announceEvent = new CustomEvent<{ provider: ChainAgnostinProvider }>( + EventNames.CORE_WALLET_ANNOUNCE_PROVIDER, + { + detail: Object.freeze({ + provider: providerInstance, + }), + } + ); + + // The Wallet dispatches an announce event which is heard by + // the DApp code that had run earlier + globalObject.dispatchEvent(announceEvent); + + // The Wallet listens to the request events which may be + // dispatched later and re-dispatches the `EIP6963AnnounceProviderEvent` + globalObject.addEventListener(EventNames.CORE_WALLET_REQUEST_PROVIDER, () => { globalObject.dispatchEvent(announceEvent); }); } diff --git a/src/background/providers/models.ts b/src/background/providers/models.ts index c952c5709..f338116ac 100644 --- a/src/background/providers/models.ts +++ b/src/background/providers/models.ts @@ -23,3 +23,10 @@ export interface EIP6963ProviderDetail { info: EIP6963ProviderInfo; provider: Eip1193Provider; } + +export enum EventNames { + CORE_WALLET_ANNOUNCE_PROVIDER = 'core-wallet:announceProvider', + CORE_WALLET_REQUEST_PROVIDER = 'core-wallet:requestProvider', + EIP6963_ANNOUNCE_PROVIDER = 'eip6963:announceProvider', + EIP6963_REQUEST_PROVIDER = 'eip6963:requestProvider', +} From d62469f34cfd01fb02b6bc5b0e3cc414d9c630f9 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Tue, 30 Jul 2024 16:44:44 +0200 Subject: [PATCH 2/9] test: initialize provider --- src/background/providers/CoreProvider.test.ts | 9 ------ .../initializeInpageProvider.test.ts | 31 +++++++++++++------ 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/background/providers/CoreProvider.test.ts b/src/background/providers/CoreProvider.test.ts index 8fc805cea..350d5d53c 100644 --- a/src/background/providers/CoreProvider.test.ts +++ b/src/background/providers/CoreProvider.test.ts @@ -457,15 +457,6 @@ describe('src/background/providers/CoreProvider', () => { await new Promise(process.nextTick); expect(chainChangedSubscription).not.toHaveBeenCalled(); - - console.log( - '(channelMock.req as jest.Mock).mock.: ', - (channelMock.request as jest.Mock).mock - ); - console.log( - '(channelMock.on as jest.Mock).mock.: ', - (channelMock.on as jest.Mock).mock - ); (channelMock.on as jest.Mock).mock.calls[0]?.[1]({ method: 'chainChanged', params: { chainId: '0x1', networkVersion: '1' }, diff --git a/src/background/providers/initializeInpageProvider.test.ts b/src/background/providers/initializeInpageProvider.test.ts index abd66f007..dc9d9310b 100644 --- a/src/background/providers/initializeInpageProvider.test.ts +++ b/src/background/providers/initializeInpageProvider.test.ts @@ -3,7 +3,15 @@ import { CoreProvider } from './CoreProvider'; import { createMultiWalletProxy } from './MultiWalletProviderProxy'; import { initializeProvider } from './initializeInpageProvider'; -jest.mock('../utils/messaging/AutoPairingPostMessageConnection'); +jest.mock('../utils/messaging/AutoPairingPostMessageConnection', () => { + const mocks = { + connect: jest.fn().mockResolvedValue(undefined), + on: jest.fn(), + request: jest.fn().mockResolvedValue({}), + }; + return jest.fn().mockReturnValue(mocks); +}); + jest.mock('./CoreProvider', () => ({ CoreProvider: jest .fn() @@ -176,10 +184,13 @@ describe('src/background/providers/initializeInpageProvider', () => { it('announces core provider with eip6963:announceProvider', () => { const provider = initializeProvider(connectionMock, 10, windowMock); - expect(windowMock.dispatchEvent).toHaveBeenCalledTimes(4); + expect(windowMock.dispatchEvent).toHaveBeenCalledTimes(5); expect(windowMock.dispatchEvent.mock.calls[3][0].type).toEqual( 'eip6963:announceProvider' ); + expect(windowMock.dispatchEvent.mock.calls[4][0].type).toEqual( + 'core-wallet:announceProvider' + ); expect(windowMock.dispatchEvent.mock.calls[3][0].detail).toEqual({ info: provider.info, provider: provider, @@ -187,10 +198,11 @@ describe('src/background/providers/initializeInpageProvider', () => { }); it('re-announces on eip6963:requestProvider', () => { const provider = initializeProvider(connectionMock, 10, windowMock); + console.log('provider: ', provider); - expect(windowMock.dispatchEvent).toHaveBeenCalledTimes(4); + expect(windowMock.dispatchEvent).toHaveBeenCalledTimes(5); - expect(windowMock.addEventListener).toHaveBeenCalledTimes(1); + expect(windowMock.addEventListener).toHaveBeenCalledTimes(2); expect(windowMock.addEventListener).toHaveBeenCalledWith( 'eip6963:requestProvider', expect.anything() @@ -198,15 +210,14 @@ describe('src/background/providers/initializeInpageProvider', () => { windowMock.addEventListener.mock.calls[0][1](); - expect(windowMock.dispatchEvent).toHaveBeenCalledTimes(5); + expect(windowMock.dispatchEvent).toHaveBeenCalledTimes(6); - expect(windowMock.dispatchEvent.mock.calls[4][0].type).toEqual( + expect(windowMock.dispatchEvent.mock.calls[3][0].type).toEqual( 'eip6963:announceProvider' ); - expect(windowMock.dispatchEvent.mock.calls[4][0].detail).toEqual({ - info: provider.info, - provider: provider, - }); + expect(windowMock.dispatchEvent.mock.calls[4][0].type).toEqual( + 'core-wallet:announceProvider' + ); }); }); }); From aaac26b369d7066dec888f8233e41844b4fe7e0b Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Wed, 31 Jul 2024 21:37:17 +0200 Subject: [PATCH 3/9] fix: providers and test cases --- .../providers/ChainAgnosticProvider.ts | 5 + src/background/providers/CoreProvider.test.ts | 303 +++--------------- src/background/providers/CoreProvider.ts | 20 +- .../MultiWalletProviderProxy.test.ts | 29 +- .../initializeInpageProvider.test.ts | 8 +- .../providers/initializeInpageProvider.ts | 2 +- 6 files changed, 71 insertions(+), 296 deletions(-) diff --git a/src/background/providers/ChainAgnosticProvider.ts b/src/background/providers/ChainAgnosticProvider.ts index 46689c823..b64be45ef 100644 --- a/src/background/providers/ChainAgnosticProvider.ts +++ b/src/background/providers/ChainAgnosticProvider.ts @@ -68,14 +68,19 @@ export class ChainAgnostinProvider extends EventEmitter { }; request = async ({ + internal, data, sessionId, chainId, }: { + internal?: boolean; data: PartialBy; sessionId: string; chainId: string | null; }) => { + if (internal) { + return this.#request({ data, chainId, sessionId }); + } return this.#requestRateLimiter.call(data.method, () => this.#request({ data, chainId, sessionId }) ); diff --git a/src/background/providers/CoreProvider.test.ts b/src/background/providers/CoreProvider.test.ts index 350d5d53c..46b9d076f 100644 --- a/src/background/providers/CoreProvider.test.ts +++ b/src/background/providers/CoreProvider.test.ts @@ -42,7 +42,7 @@ describe('src/background/providers/CoreProvider', () => { describe('EIP-5749', () => { it('sets the ProviderInfo', () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); expect(provider.info).toEqual({ description: 'EVM_PROVIDER_INFO_DESCRIPTION', icon: 'EVM_PROVIDER_INFO_ICON', @@ -56,7 +56,7 @@ describe('src/background/providers/CoreProvider', () => { describe('EIP-1193', () => { describe('request', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -79,9 +79,6 @@ describe('src/background/providers/CoreProvider', () => { }) ); - // response for domain metadata send - // TODO: invastigate why it has skipped when all the cases are running - //(channelMock.request as jest.Mock).mockResolvedValueOnce({}); // response for 'some-method' (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); const rpcResultCallback = jest.fn(); @@ -111,8 +108,8 @@ describe('src/background/providers/CoreProvider', () => { expect(rpcResultCallback).toHaveBeenCalledWith('success'); }); - it.skip('always returns JSON RPC-compatible error', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('always returns JSON RPC-compatible error', async () => { + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -163,17 +160,11 @@ describe('src/background/providers/CoreProvider', () => { }) ); - expect(callCallback).toHaveBeenCalledWith({ - code: -32603, - data: { - originalError: {}, - }, - message: 'non RPC error', - }); + expect(callCallback).toHaveBeenCalledWith(new Error('non RPC error')); }); it('does not double wraps JSON RPC errors', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -239,7 +230,7 @@ describe('src/background/providers/CoreProvider', () => { describe('events', () => { describe(`connect`, () => { it('emits `connect` when chainId first set', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -277,7 +268,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -316,7 +307,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits connect on re-connect after disconnected', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -363,7 +354,7 @@ describe('src/background/providers/CoreProvider', () => { describe('disconnect', () => { it('emits disconnect event with error', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -407,7 +398,7 @@ describe('src/background/providers/CoreProvider', () => { describe('chainChanged', () => { it('does not emit `chainChanged` on initialization', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -437,7 +428,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -466,7 +457,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('does not emit `chainChanged` when chain is set to the same value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -501,7 +492,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits `chainChanged` when chain is set to new value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -540,7 +531,7 @@ describe('src/background/providers/CoreProvider', () => { describe('accountsChanged', () => { it('emits `accountsChanged` on initialization', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -573,7 +564,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: '1', accounts: undefined, }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -597,7 +588,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('does not emit `accountsChanged` when account is set to the same value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -627,7 +618,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits `accountsChanged` when account is set to new value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -664,7 +655,7 @@ describe('src/background/providers/CoreProvider', () => { describe('legacy', () => { describe('sendAsync', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -715,76 +706,8 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it.skip('rate limits `eth_requestAccounts` requests', async () => { - const provider = new CoreProvider({ connection: channelMock }); - (addEventListenerSpy.mock.calls[0]?.[1] as any)({ - detail: { - provider: { - subscribeToMessage: jest.fn((callback) => { - return channelMock.on('message', callback); - }), - request: jest.fn((params) => { - return channelMock.request(params as any); - }), - }, - }, - }); - // wait for init to finish - await new Promise(process.nextTick); - - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce({}); - // response for 'eth_requestAccounts' - (channelMock.request as jest.Mock).mockResolvedValue('success'); - const firstCallCallback = jest.fn(); - const secondCallCallback = jest.fn(); - provider.sendAsync( - { - method: 'eth_requestAccounts', - }, - firstCallCallback - ); - provider.sendAsync( - { - method: 'eth_requestAccounts', - }, - secondCallCallback - ); - await new Promise(process.nextTick); - - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); - expect(channelMock.request).toHaveBeenCalledWith( - matchingPayload({ - method: 'eth_requestAccounts', - }) - ); - - expect(firstCallCallback).toHaveBeenCalledWith(null, { - method: 'eth_requestAccounts', - result: 'success', - }); - expect(secondCallCallback).toHaveBeenCalledWith( - ethErrors.rpc.resourceUnavailable( - `Request of type eth_requestAccounts already pending for origin. Please wait.` - ), - { - method: 'eth_requestAccounts', - error: ethErrors.rpc.resourceUnavailable( - `Request of type eth_requestAccounts already pending for origin. Please wait.` - ), - } - ); - }); - it('supports batched requets', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -848,7 +771,7 @@ describe('src/background/providers/CoreProvider', () => { describe('send', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -899,76 +822,8 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it.skip('rate limits `eth_requestAccounts` requests', async () => { - const provider = new CoreProvider({ connection: channelMock }); - (addEventListenerSpy.mock.calls[0]?.[1] as any)({ - detail: { - provider: { - subscribeToMessage: jest.fn((callback) => { - return channelMock.on('message', callback); - }), - request: jest.fn((params) => { - return channelMock.request(params as any); - }), - }, - }, - }); - // wait for init to finish - await new Promise(process.nextTick); - - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce({}); - // response for 'eth_requestAccounts' - (channelMock.request as jest.Mock).mockResolvedValue('success'); - const firstCallCallback = jest.fn(); - const secondCallCallback = jest.fn(); - provider.send( - { - method: 'eth_requestAccounts', - }, - firstCallCallback - ); - provider.send( - { - method: 'eth_requestAccounts', - }, - secondCallCallback - ); - await new Promise(process.nextTick); - - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); - expect(channelMock.request).toHaveBeenCalledWith( - matchingPayload({ - method: 'eth_requestAccounts', - }) - ); - - expect(firstCallCallback).toHaveBeenCalledWith(null, { - method: 'eth_requestAccounts', - result: 'success', - }); - expect(secondCallCallback).toHaveBeenCalledWith( - ethErrors.rpc.resourceUnavailable( - `Request of type eth_requestAccounts already pending for origin. Please wait.` - ), - { - method: 'eth_requestAccounts', - error: ethErrors.rpc.resourceUnavailable( - `Request of type eth_requestAccounts already pending for origin. Please wait.` - ), - } - ); - }); - it('supports batched requets', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1030,7 +885,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('supports method as the only param', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1078,7 +933,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('supports method with params', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1126,7 +981,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('returns eth_accounts response syncronously', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1153,7 +1008,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('returns eth_coinbase response syncronously', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1180,7 +1035,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('throws error if method not supported syncronously', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1216,7 +1071,7 @@ describe('src/background/providers/CoreProvider', () => { describe('enable', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1256,60 +1111,11 @@ describe('src/background/providers/CoreProvider', () => { expect(rpcResultCallback).toHaveBeenCalledWith(['0x0000']); }); - - it.skip('rate limits enable calls', async () => { - const provider = new CoreProvider({ connection: channelMock }); - (addEventListenerSpy.mock.calls[0]?.[1] as any)({ - detail: { - provider: { - subscribeToMessage: jest.fn((callback) => { - return channelMock.on('message', callback); - }), - request: jest.fn((params) => { - return channelMock.request(params as any); - }), - }, - }, - }); - // wait for init to finish - await new Promise(process.nextTick); - - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce(undefined); - // response for 'eth_requestAccounts' - (channelMock.request as jest.Mock).mockResolvedValue(['0x0000']); - const firstCallCallback = jest.fn(); - const secondCallCallback = jest.fn(); - provider.enable().then(firstCallCallback).catch(firstCallCallback); - provider.enable().then(secondCallCallback).catch(secondCallCallback); - await new Promise(process.nextTick); - - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); - expect(channelMock.request).toHaveBeenCalledWith( - matchingPayload({ - method: 'eth_requestAccounts', - }) - ); - - expect(firstCallCallback).toHaveBeenCalledWith(['0x0000']); - expect(secondCallCallback).toHaveBeenCalledWith( - ethErrors.rpc.resourceUnavailable( - `Request of type eth_requestAccounts already pending for origin. Please wait.` - ) - ); - }); }); describe('net_version', () => { it('supports net_version call', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1353,7 +1159,7 @@ describe('src/background/providers/CoreProvider', () => { describe('close event', () => { it('emits close event with error', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1397,7 +1203,7 @@ describe('src/background/providers/CoreProvider', () => { describe('networkChanged event', () => { it('does not emit `networkChanged` on initialization', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1427,7 +1233,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1457,7 +1263,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('does not emit `networkChanged` when chain is set to the same value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1492,7 +1298,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits `chainChanged` when chain is set to new value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1533,7 +1339,7 @@ describe('src/background/providers/CoreProvider', () => { describe('init', () => { it('should call the event listener with the right event name', async () => { - new CoreProvider({ connection: channelMock }); + new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1553,37 +1359,6 @@ describe('src/background/providers/CoreProvider', () => { expect.any(Function) ); }); - it.skip('waits for message channel to be connected', async () => { - const mockedChannel = new AutoPairingPostMessageConnection(false); - let resolve; - const promise = new Promise((res) => { - resolve = res; - }); - - jest.mocked(mockedChannel.connect).mockReturnValue(promise); - - new CoreProvider({ connection: mockedChannel }); - - (addEventListenerSpy.mock.calls[0]?.[1] as any)({ - detail: { - provider: { - subscribeToMessage: jest.fn((callback) => { - return channelMock.on('message', callback); - }), - request: jest.fn((params) => { - return channelMock.request(params as any); - }), - }, - }, - }); - expect(mockedChannel.connect).toHaveBeenCalled(); - expect(mockedChannel.request).not.toHaveBeenCalled(); - - resolve(); - await new Promise(process.nextTick); - expect(mockedChannel.request).toHaveBeenCalled(); - }); - it('loads provider state from the background', async () => { const mockedChannel = new AutoPairingPostMessageConnection(false); @@ -1594,7 +1369,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: '1', accounts: ['0x00000'], }); - const provider = new CoreProvider({ connection: mockedChannel }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1633,7 +1408,7 @@ describe('src/background/providers/CoreProvider', () => { describe('Metamask compatibility', () => { it('supports _metamask.isUnlocked', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1653,11 +1428,11 @@ describe('src/background/providers/CoreProvider', () => { expect(await provider._metamask.isUnlocked()).toBe(true); }); it('isMetamask is true', () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); expect(provider.isMetaMask).toBe(true); }); it('isAvalanche is true', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); expect(provider.isAvalanche).toBe(true); }); }); diff --git a/src/background/providers/CoreProvider.ts b/src/background/providers/CoreProvider.ts index 6744d35a1..0bfb190bf 100644 --- a/src/background/providers/CoreProvider.ts +++ b/src/background/providers/CoreProvider.ts @@ -17,7 +17,6 @@ import { type JsonRpcRequestPayload, } from '../connections/dAppConnection/models'; import type { PartialBy, ProviderInfo } from '../models'; -import type AbstractConnection from '../utils/messaging/AbstractConnection'; import { ChainAgnostinProvider } from './ChainAgnosticProvider'; interface ProviderState { @@ -68,12 +67,7 @@ export class CoreProvider extends EventEmitter { isUnlocked: () => Promise.resolve(this._isUnlocked), }; - constructor({ - maxListeners = 100, - }: { - connection: AbstractConnection; - maxListeners?: number; - }) { + constructor(maxListeners: number = 100) { super(); this.setMaxListeners(maxListeners); this.#subscribe(); @@ -117,6 +111,7 @@ export class CoreProvider extends EventEmitter { const response = await this.#requestInternal({ method: DAppProviderRequest.INIT_DAPP_STATE, }); + const { chainId, accounts, networkVersion, isUnlocked } = (response as { isUnlocked: boolean; @@ -170,8 +165,15 @@ export class CoreProvider extends EventEmitter { }); }; - #requestInternal = (data) => { - return this.#request(data); + #requestInternal = ( + data: PartialBy + ) => { + return this.#chainagnosticProvider?.request({ + internal: true, + data, + chainId: this.chainId, + sessionId: this._sessionId, + }); }; #getEventHandler = (method: string): ((params: any) => void) => { diff --git a/src/background/providers/MultiWalletProviderProxy.test.ts b/src/background/providers/MultiWalletProviderProxy.test.ts index e5fc80684..63b8820a4 100644 --- a/src/background/providers/MultiWalletProviderProxy.test.ts +++ b/src/background/providers/MultiWalletProviderProxy.test.ts @@ -4,7 +4,6 @@ import { MultiWalletProviderProxy, createMultiWalletProxy, } from './MultiWalletProviderProxy'; -import AutoPairingPostMessageConnection from '../utils/messaging/AutoPairingPostMessageConnection'; jest.mock('../utils/messaging/AutoPairingPostMessageConnection'); jest.mock('./CoreProvider', () => ({ @@ -16,11 +15,9 @@ jest.mock('./CoreProvider', () => ({ })); describe('src/background/providers/MultiWalletProviderProxy', () => { - const connectionMock = new AutoPairingPostMessageConnection(false); - describe('init', () => { it('initializes with the default provider', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); @@ -46,7 +43,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('addProvider', () => { it('adds new providers from coinbase proxy', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -73,7 +70,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not add extra coinbase proxy', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -90,7 +87,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('adds new provider', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -107,7 +104,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('wallet selection', () => { it('toggles wallet selection on `eth_requestAccounts` call if multiple providers', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -154,7 +151,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not toggle wallet selection if only core is available', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); provider.request = jest.fn().mockResolvedValueOnce(['0x000000']); @@ -177,7 +174,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not toggle wallet selection if wallet is already selected', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -233,7 +230,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: enable', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, enable: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -275,7 +272,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: sendAsync', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -329,7 +326,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: send with callback', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -381,7 +378,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('createMultiWalletProxy', () => { it('creates proxy with property deletion disabled', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = createMultiWalletProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -390,7 +387,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('allows setting extra params without changing the provider', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = createMultiWalletProxy(provider); (mwpp as any).somePromerty = true; @@ -414,7 +411,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('maintains the providers list properly', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = createMultiWalletProxy(provider); const fooMock = () => 'bar'; const bizMock = () => 'baz'; diff --git a/src/background/providers/initializeInpageProvider.test.ts b/src/background/providers/initializeInpageProvider.test.ts index dc9d9310b..d5e9d386e 100644 --- a/src/background/providers/initializeInpageProvider.test.ts +++ b/src/background/providers/initializeInpageProvider.test.ts @@ -35,10 +35,7 @@ describe('src/background/providers/initializeInpageProvider', () => { it('initializes CoreProvider with the correct channel name', () => { const provider = initializeProvider(connectionMock, 10, windowMock); - expect(CoreProvider).toHaveBeenCalledWith({ - connection: connectionMock, - maxListeners: 10, - }); + expect(CoreProvider).toHaveBeenCalledWith(10); expect(provider.isAvalanche).toBe(true); }); @@ -197,8 +194,7 @@ describe('src/background/providers/initializeInpageProvider', () => { }); }); it('re-announces on eip6963:requestProvider', () => { - const provider = initializeProvider(connectionMock, 10, windowMock); - console.log('provider: ', provider); + initializeProvider(connectionMock, 10, windowMock); expect(windowMock.dispatchEvent).toHaveBeenCalledTimes(5); diff --git a/src/background/providers/initializeInpageProvider.ts b/src/background/providers/initializeInpageProvider.ts index f9b8ab93d..f8f7a096e 100644 --- a/src/background/providers/initializeInpageProvider.ts +++ b/src/background/providers/initializeInpageProvider.ts @@ -24,7 +24,7 @@ export function initializeProvider( } ); - const provider = new Proxy(new CoreProvider({ connection, maxListeners }), { + const provider = new Proxy(new CoreProvider(maxListeners), { // some common libraries, e.g. web3@1.x, mess with our API deleteProperty: () => true, }); From 764249ce8953ead3d5a7829aee18b31bcaf878e2 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Thu, 1 Aug 2024 16:48:05 +0200 Subject: [PATCH 4/9] fix: make connection and test cases work --- .../providers/ChainAgnosticProvider.ts | 4 +- src/background/providers/CoreProvider.test.ts | 74 +++++++++---------- src/background/providers/CoreProvider.ts | 29 ++++---- .../MultiWalletProviderProxy.test.ts | 28 +++---- .../initializeInpageProvider.test.ts | 5 +- .../providers/initializeInpageProvider.ts | 2 +- 6 files changed, 73 insertions(+), 69 deletions(-) diff --git a/src/background/providers/ChainAgnosticProvider.ts b/src/background/providers/ChainAgnosticProvider.ts index b64be45ef..232414d17 100644 --- a/src/background/providers/ChainAgnosticProvider.ts +++ b/src/background/providers/ChainAgnosticProvider.ts @@ -33,8 +33,8 @@ export class ChainAgnostinProvider extends EventEmitter { chainId, }: { data: PartialBy; - sessionId: string; - chainId: string | null; + sessionId?: string; + chainId?: string | null; }) => { if (!data) { throw ethErrors.rpc.invalidRequest(); diff --git a/src/background/providers/CoreProvider.test.ts b/src/background/providers/CoreProvider.test.ts index 46b9d076f..93698e67e 100644 --- a/src/background/providers/CoreProvider.test.ts +++ b/src/background/providers/CoreProvider.test.ts @@ -42,7 +42,7 @@ describe('src/background/providers/CoreProvider', () => { describe('EIP-5749', () => { it('sets the ProviderInfo', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); expect(provider.info).toEqual({ description: 'EVM_PROVIDER_INFO_DESCRIPTION', icon: 'EVM_PROVIDER_INFO_ICON', @@ -56,7 +56,7 @@ describe('src/background/providers/CoreProvider', () => { describe('EIP-1193', () => { describe('request', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -109,7 +109,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('always returns JSON RPC-compatible error', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -164,7 +164,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('does not double wraps JSON RPC errors', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -230,7 +230,7 @@ describe('src/background/providers/CoreProvider', () => { describe('events', () => { describe(`connect`, () => { it('emits `connect` when chainId first set', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -268,7 +268,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -307,7 +307,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits connect on re-connect after disconnected', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -354,7 +354,7 @@ describe('src/background/providers/CoreProvider', () => { describe('disconnect', () => { it('emits disconnect event with error', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -398,7 +398,7 @@ describe('src/background/providers/CoreProvider', () => { describe('chainChanged', () => { it('does not emit `chainChanged` on initialization', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -428,7 +428,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -457,7 +457,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('does not emit `chainChanged` when chain is set to the same value', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -492,7 +492,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits `chainChanged` when chain is set to new value', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -531,7 +531,7 @@ describe('src/background/providers/CoreProvider', () => { describe('accountsChanged', () => { it('emits `accountsChanged` on initialization', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -564,7 +564,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: '1', accounts: undefined, }); - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -588,7 +588,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('does not emit `accountsChanged` when account is set to the same value', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -618,7 +618,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits `accountsChanged` when account is set to new value', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -655,7 +655,7 @@ describe('src/background/providers/CoreProvider', () => { describe('legacy', () => { describe('sendAsync', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -707,7 +707,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('supports batched requets', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -771,7 +771,7 @@ describe('src/background/providers/CoreProvider', () => { describe('send', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -823,7 +823,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('supports batched requets', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -885,7 +885,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('supports method as the only param', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -933,7 +933,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('supports method with params', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -981,7 +981,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('returns eth_accounts response syncronously', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1008,7 +1008,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('returns eth_coinbase response syncronously', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1035,7 +1035,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('throws error if method not supported syncronously', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1071,7 +1071,7 @@ describe('src/background/providers/CoreProvider', () => { describe('enable', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1115,7 +1115,7 @@ describe('src/background/providers/CoreProvider', () => { describe('net_version', () => { it('supports net_version call', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1159,7 +1159,7 @@ describe('src/background/providers/CoreProvider', () => { describe('close event', () => { it('emits close event with error', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1203,7 +1203,7 @@ describe('src/background/providers/CoreProvider', () => { describe('networkChanged event', () => { it('does not emit `networkChanged` on initialization', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1233,7 +1233,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1263,7 +1263,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('does not emit `networkChanged` when chain is set to the same value', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1298,7 +1298,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits `chainChanged` when chain is set to new value', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1339,7 +1339,7 @@ describe('src/background/providers/CoreProvider', () => { describe('init', () => { it('should call the event listener with the right event name', async () => { - new CoreProvider(); + new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1369,7 +1369,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: '1', accounts: ['0x00000'], }); - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1408,7 +1408,7 @@ describe('src/background/providers/CoreProvider', () => { describe('Metamask compatibility', () => { it('supports _metamask.isUnlocked', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1428,11 +1428,11 @@ describe('src/background/providers/CoreProvider', () => { expect(await provider._metamask.isUnlocked()).toBe(true); }); it('isMetamask is true', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); expect(provider.isMetaMask).toBe(true); }); it('isAvalanche is true', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: channelMock }); expect(provider.isAvalanche).toBe(true); }); }); diff --git a/src/background/providers/CoreProvider.ts b/src/background/providers/CoreProvider.ts index 0bfb190bf..7fd198453 100644 --- a/src/background/providers/CoreProvider.ts +++ b/src/background/providers/CoreProvider.ts @@ -18,6 +18,7 @@ import { } from '../connections/dAppConnection/models'; import type { PartialBy, ProviderInfo } from '../models'; import { ChainAgnostinProvider } from './ChainAgnosticProvider'; +import AbstractConnection from '../utils/messaging/AbstractConnection'; interface ProviderState { accounts: string[] | null; @@ -28,7 +29,7 @@ interface ProviderState { export class CoreProvider extends EventEmitter { #providerReadyPromise = new ProviderReadyPromise(); - + #contentScriptConnection: AbstractConnection; #chainagnosticProvider?: ChainAgnostinProvider; readonly info: ProviderInfo = { @@ -67,9 +68,16 @@ export class CoreProvider extends EventEmitter { isUnlocked: () => Promise.resolve(this._isUnlocked), }; - constructor(maxListeners: number = 100) { + constructor({ + connection, + maxListeners = 100, + }: { + connection: AbstractConnection; + maxListeners?: number; + }) { super(); this.setMaxListeners(maxListeners); + this.#contentScriptConnection = connection; this.#subscribe(); } @@ -96,10 +104,12 @@ export class CoreProvider extends EventEmitter { * Initializes provider state, and collects dApp information */ #init = async () => { + await this.#contentScriptConnection.connect(); + onDomReady(async () => { const domainMetadata = await getSiteMetadata(); - this.#requestInternal({ + this.#request({ method: DAppProviderRequest.DOMAIN_METADATA_METHOD, params: domainMetadata, }); @@ -108,7 +118,7 @@ export class CoreProvider extends EventEmitter { }); try { - const response = await this.#requestInternal({ + const response = await this.#request({ method: DAppProviderRequest.INIT_DAPP_STATE, }); @@ -165,17 +175,6 @@ export class CoreProvider extends EventEmitter { }); }; - #requestInternal = ( - data: PartialBy - ) => { - return this.#chainagnosticProvider?.request({ - internal: true, - data, - chainId: this.chainId, - sessionId: this._sessionId, - }); - }; - #getEventHandler = (method: string): ((params: any) => void) => { const handlerMap = { connect: this.#connect, diff --git a/src/background/providers/MultiWalletProviderProxy.test.ts b/src/background/providers/MultiWalletProviderProxy.test.ts index 63b8820a4..31e8b4dac 100644 --- a/src/background/providers/MultiWalletProviderProxy.test.ts +++ b/src/background/providers/MultiWalletProviderProxy.test.ts @@ -4,6 +4,7 @@ import { MultiWalletProviderProxy, createMultiWalletProxy, } from './MultiWalletProviderProxy'; +import AutoPairingPostMessageConnection from '../utils/messaging/AutoPairingPostMessageConnection'; jest.mock('../utils/messaging/AutoPairingPostMessageConnection'); jest.mock('./CoreProvider', () => ({ @@ -15,9 +16,10 @@ jest.mock('./CoreProvider', () => ({ })); describe('src/background/providers/MultiWalletProviderProxy', () => { + const connectionMock = new AutoPairingPostMessageConnection(false); describe('init', () => { it('initializes with the default provider', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const mwpp = new MultiWalletProviderProxy(provider); @@ -43,7 +45,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('addProvider', () => { it('adds new providers from coinbase proxy', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -70,7 +72,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not add extra coinbase proxy', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -87,7 +89,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('adds new provider', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -104,7 +106,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('wallet selection', () => { it('toggles wallet selection on `eth_requestAccounts` call if multiple providers', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -151,7 +153,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not toggle wallet selection if only core is available', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const mwpp = new MultiWalletProviderProxy(provider); provider.request = jest.fn().mockResolvedValueOnce(['0x000000']); @@ -174,7 +176,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not toggle wallet selection if wallet is already selected', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -230,7 +232,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: enable', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const provider2 = { isMetaMask: true, enable: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -272,7 +274,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: sendAsync', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -326,7 +328,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: send with callback', async () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -378,7 +380,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('createMultiWalletProxy', () => { it('creates proxy with property deletion disabled', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const mwpp = createMultiWalletProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -387,7 +389,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('allows setting extra params without changing the provider', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const mwpp = createMultiWalletProxy(provider); (mwpp as any).somePromerty = true; @@ -411,7 +413,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('maintains the providers list properly', () => { - const provider = new CoreProvider(); + const provider = new CoreProvider({ connection: connectionMock }); const mwpp = createMultiWalletProxy(provider); const fooMock = () => 'bar'; const bizMock = () => 'baz'; diff --git a/src/background/providers/initializeInpageProvider.test.ts b/src/background/providers/initializeInpageProvider.test.ts index d5e9d386e..6b47d4ddd 100644 --- a/src/background/providers/initializeInpageProvider.test.ts +++ b/src/background/providers/initializeInpageProvider.test.ts @@ -35,7 +35,10 @@ describe('src/background/providers/initializeInpageProvider', () => { it('initializes CoreProvider with the correct channel name', () => { const provider = initializeProvider(connectionMock, 10, windowMock); - expect(CoreProvider).toHaveBeenCalledWith(10); + expect(CoreProvider).toHaveBeenCalledWith({ + connection: connectionMock, + maxListeners: 10, + }); expect(provider.isAvalanche).toBe(true); }); diff --git a/src/background/providers/initializeInpageProvider.ts b/src/background/providers/initializeInpageProvider.ts index f8f7a096e..c654396b8 100644 --- a/src/background/providers/initializeInpageProvider.ts +++ b/src/background/providers/initializeInpageProvider.ts @@ -24,7 +24,7 @@ export function initializeProvider( } ); - const provider = new Proxy(new CoreProvider(maxListeners), { + const provider = new Proxy(new CoreProvider({ maxListeners, connection }), { // some common libraries, e.g. web3@1.x, mess with our API deleteProperty: () => true, }); From 85937b9e2fa411cc05ef857f121d9e68ae9e1479 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Thu, 1 Aug 2024 16:59:18 +0200 Subject: [PATCH 5/9] chore: renaming --- src/background/providers/ChainAgnosticProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/providers/ChainAgnosticProvider.ts b/src/background/providers/ChainAgnosticProvider.ts index 232414d17..76a01bb58 100644 --- a/src/background/providers/ChainAgnosticProvider.ts +++ b/src/background/providers/ChainAgnosticProvider.ts @@ -6,7 +6,7 @@ import { import { PartialBy } from '../models'; import { ethErrors, serializeError } from 'eth-rpc-errors'; import AbstractConnection from '../utils/messaging/AbstractConnection'; -import { ChainId } from '@avalabs/chains-sdk'; +import { ChainId } from '@avalabs/core-chains-sdk'; import RequestRatelimiter from './utils/RequestRatelimiter'; export class ChainAgnostinProvider extends EventEmitter { From b56bc22e48c068ed985fe44f935a5b7ddbe45305 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Mon, 5 Aug 2024 08:43:30 +0200 Subject: [PATCH 6/9] fix: name and remove unused param --- .../providers/ChainAgnosticProvider.test.ts | 14 +++++++------- src/background/providers/ChainAgnosticProvider.ts | 7 +------ src/background/providers/CoreProvider.ts | 4 ++-- .../providers/initializeInpageProvider.ts | 8 ++++---- 4 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/background/providers/ChainAgnosticProvider.test.ts b/src/background/providers/ChainAgnosticProvider.test.ts index b2723a7f3..f2f4b78d1 100644 --- a/src/background/providers/ChainAgnosticProvider.test.ts +++ b/src/background/providers/ChainAgnosticProvider.test.ts @@ -1,6 +1,6 @@ import { ethErrors } from 'eth-rpc-errors'; import AutoPairingPostMessageConnection from '../utils/messaging/AutoPairingPostMessageConnection'; -import { ChainAgnostinProvider } from './ChainAgnosticProvider'; +import { ChainAgnosticProvider } from './ChainAgnosticProvider'; jest.mock('./utils/onDomReady'); jest.mock('../utils/messaging/AutoPairingPostMessageConnection', () => { @@ -16,7 +16,7 @@ describe('src/background/providers/ChainAgnosticProvider', () => { describe('initialization', () => { it('should connect to the backgroundscript', async () => { - new ChainAgnostinProvider(channelMock); + new ChainAgnosticProvider(channelMock); expect(channelMock.connect).toHaveBeenCalled(); expect(channelMock.request).not.toHaveBeenCalled(); @@ -24,7 +24,7 @@ describe('src/background/providers/ChainAgnosticProvider', () => { it('waits for message channel to be connected', async () => { const mockedChannel = new AutoPairingPostMessageConnection(false); - const provider = new ChainAgnostinProvider(channelMock); + const provider = new ChainAgnosticProvider(channelMock); expect(mockedChannel.connect).toHaveBeenCalled(); expect(mockedChannel.request).not.toHaveBeenCalled(); @@ -39,7 +39,7 @@ describe('src/background/providers/ChainAgnosticProvider', () => { describe('request', () => { it('should use the rate limits on `eth_requestAccounts` requests', async () => { - const provider = new ChainAgnostinProvider(channelMock); + const provider = new ChainAgnosticProvider(channelMock); (channelMock.request as jest.Mock).mockResolvedValue('success'); const firstCallCallback = jest.fn(); @@ -66,7 +66,7 @@ describe('src/background/providers/ChainAgnosticProvider', () => { ); }); it('shoud not use the rate limits on `random_method` requests', async () => { - const provider = new ChainAgnostinProvider(channelMock); + const provider = new ChainAgnosticProvider(channelMock); (channelMock.request as jest.Mock).mockResolvedValue('success'); const firstCallCallback = jest.fn(); @@ -90,7 +90,7 @@ describe('src/background/providers/ChainAgnosticProvider', () => { }); it('should call the request of the connection', async () => { - const provider = new ChainAgnostinProvider(channelMock); + const provider = new ChainAgnosticProvider(channelMock); (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); await provider.request({ @@ -102,7 +102,7 @@ describe('src/background/providers/ChainAgnosticProvider', () => { }); describe('CAIP-27', () => { it('should wrap the incoming request into CAIP-27 envelope and reuses the provided ID', async () => { - const provider = new ChainAgnostinProvider(channelMock); + const provider = new ChainAgnosticProvider(channelMock); // response for the actual call (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); diff --git a/src/background/providers/ChainAgnosticProvider.ts b/src/background/providers/ChainAgnosticProvider.ts index 76a01bb58..6475a55f7 100644 --- a/src/background/providers/ChainAgnosticProvider.ts +++ b/src/background/providers/ChainAgnosticProvider.ts @@ -9,7 +9,7 @@ import AbstractConnection from '../utils/messaging/AbstractConnection'; import { ChainId } from '@avalabs/core-chains-sdk'; import RequestRatelimiter from './utils/RequestRatelimiter'; -export class ChainAgnostinProvider extends EventEmitter { +export class ChainAgnosticProvider extends EventEmitter { #contentScriptConnection: AbstractConnection; #requestRateLimiter = new RequestRatelimiter([ @@ -68,19 +68,14 @@ export class ChainAgnostinProvider extends EventEmitter { }; request = async ({ - internal, data, sessionId, chainId, }: { - internal?: boolean; data: PartialBy; sessionId: string; chainId: string | null; }) => { - if (internal) { - return this.#request({ data, chainId, sessionId }); - } return this.#requestRateLimiter.call(data.method, () => this.#request({ data, chainId, sessionId }) ); diff --git a/src/background/providers/CoreProvider.ts b/src/background/providers/CoreProvider.ts index 7fd198453..59ed12d33 100644 --- a/src/background/providers/CoreProvider.ts +++ b/src/background/providers/CoreProvider.ts @@ -17,7 +17,7 @@ import { type JsonRpcRequestPayload, } from '../connections/dAppConnection/models'; import type { PartialBy, ProviderInfo } from '../models'; -import { ChainAgnostinProvider } from './ChainAgnosticProvider'; +import { ChainAgnosticProvider } from './ChainAgnosticProvider'; import AbstractConnection from '../utils/messaging/AbstractConnection'; interface ProviderState { @@ -30,7 +30,7 @@ interface ProviderState { export class CoreProvider extends EventEmitter { #providerReadyPromise = new ProviderReadyPromise(); #contentScriptConnection: AbstractConnection; - #chainagnosticProvider?: ChainAgnostinProvider; + #chainagnosticProvider?: ChainAgnosticProvider; readonly info: ProviderInfo = { name: EVM_PROVIDER_INFO_NAME, diff --git a/src/background/providers/initializeInpageProvider.ts b/src/background/providers/initializeInpageProvider.ts index c654396b8..be30cd57b 100644 --- a/src/background/providers/initializeInpageProvider.ts +++ b/src/background/providers/initializeInpageProvider.ts @@ -1,5 +1,5 @@ import type AbstractConnection from '../utils/messaging/AbstractConnection'; -import { ChainAgnostinProvider } from './ChainAgnosticProvider'; +import { ChainAgnosticProvider } from './ChainAgnosticProvider'; import { CoreProvider } from './CoreProvider'; import { createMultiWalletProxy } from './MultiWalletProviderProxy'; import { EventNames, type EIP6963ProviderDetail } from './models'; @@ -18,7 +18,7 @@ export function initializeProvider( globalObject = window ): CoreProvider { const chainAgnosticProvider = new Proxy( - new ChainAgnostinProvider(connection), + new ChainAgnosticProvider(connection), { deleteProperty: () => true, } @@ -149,10 +149,10 @@ function announceWalletProvider( } function announceChainAgnosticProvider( - providerInstance: ChainAgnostinProvider, + providerInstance: ChainAgnosticProvider, globalObject = window ): void { - const announceEvent = new CustomEvent<{ provider: ChainAgnostinProvider }>( + const announceEvent = new CustomEvent<{ provider: ChainAgnosticProvider }>( EventNames.CORE_WALLET_ANNOUNCE_PROVIDER, { detail: Object.freeze({ From d8c204f4ea01ddefb68dda97d2719c78a72ec1cc Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Tue, 6 Aug 2024 17:59:25 +0200 Subject: [PATCH 7/9] feat: providers loading ready --- .../providers/ChainAgnosticProvider.ts | 36 ++++++++++++++++--- src/background/providers/CoreProvider.ts | 30 +++------------- .../providers/initializeInpageProvider.ts | 2 +- .../providers/utils/ProviderReadyPromise.ts | 25 +++++++------ 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/src/background/providers/ChainAgnosticProvider.ts b/src/background/providers/ChainAgnosticProvider.ts index 6475a55f7..3df1b647b 100644 --- a/src/background/providers/ChainAgnosticProvider.ts +++ b/src/background/providers/ChainAgnosticProvider.ts @@ -1,5 +1,6 @@ import EventEmitter from 'events'; import { + DAppProviderRequest, JsonRpcRequest, JsonRpcRequestPayload, } from '../connections/dAppConnection/models'; @@ -8,9 +9,18 @@ import { ethErrors, serializeError } from 'eth-rpc-errors'; import AbstractConnection from '../utils/messaging/AbstractConnection'; import { ChainId } from '@avalabs/core-chains-sdk'; import RequestRatelimiter from './utils/RequestRatelimiter'; +import { + InitializationStep, + ProviderReadyPromise, +} from './utils/ProviderReadyPromise'; +import onDomReady from './utils/onDomReady'; +import { getSiteMetadata } from './utils/getSiteMetadata'; export class ChainAgnosticProvider extends EventEmitter { #contentScriptConnection: AbstractConnection; + #providerReadyPromise = new ProviderReadyPromise([ + InitializationStep.DOMAIN_METADATA_SENT, + ]); #requestRateLimiter = new RequestRatelimiter([ 'eth_requestAccounts', @@ -19,12 +29,26 @@ export class ChainAgnosticProvider extends EventEmitter { constructor(connection) { super(); + connection.connect(); this.#contentScriptConnection = connection; this.#init(); } async #init() { await this.#contentScriptConnection.connect(); + + onDomReady(async () => { + const domainMetadata = await getSiteMetadata(); + + this.#request({ + data: { + method: DAppProviderRequest.DOMAIN_METADATA_METHOD, + params: domainMetadata, + }, + }); + + this.#providerReadyPromise.check(InitializationStep.DOMAIN_METADATA_SENT); + }); } #request = async ({ @@ -73,12 +97,14 @@ export class ChainAgnosticProvider extends EventEmitter { chainId, }: { data: PartialBy; - sessionId: string; - chainId: string | null; + sessionId?: string; + chainId?: string | null; }) => { - return this.#requestRateLimiter.call(data.method, () => - this.#request({ data, chainId, sessionId }) - ); + return this.#providerReadyPromise.call(() => { + return this.#requestRateLimiter.call(data.method, () => + this.#request({ data, chainId, sessionId }) + ); + }); }; subscribeToMessage = (callback) => { diff --git a/src/background/providers/CoreProvider.ts b/src/background/providers/CoreProvider.ts index 59ed12d33..8fbc56739 100644 --- a/src/background/providers/CoreProvider.ts +++ b/src/background/providers/CoreProvider.ts @@ -1,7 +1,5 @@ import { ethErrors } from 'eth-rpc-errors'; import EventEmitter from 'events'; -import { getSiteMetadata } from './utils/getSiteMetadata'; -import onDomReady from './utils/onDomReady'; import { EventNames, type AccountsChangedEventData, @@ -18,7 +16,6 @@ import { } from '../connections/dAppConnection/models'; import type { PartialBy, ProviderInfo } from '../models'; import { ChainAgnosticProvider } from './ChainAgnosticProvider'; -import AbstractConnection from '../utils/messaging/AbstractConnection'; interface ProviderState { accounts: string[] | null; @@ -28,8 +25,9 @@ interface ProviderState { } export class CoreProvider extends EventEmitter { - #providerReadyPromise = new ProviderReadyPromise(); - #contentScriptConnection: AbstractConnection; + #providerReadyPromise = new ProviderReadyPromise([ + InitializationStep.PROVIDER_STATE_LOADED, + ]); #chainagnosticProvider?: ChainAgnosticProvider; readonly info: ProviderInfo = { @@ -68,16 +66,9 @@ export class CoreProvider extends EventEmitter { isUnlocked: () => Promise.resolve(this._isUnlocked), }; - constructor({ - connection, - maxListeners = 100, - }: { - connection: AbstractConnection; - maxListeners?: number; - }) { + constructor({ maxListeners = 100 }: { maxListeners?: number }) { super(); this.setMaxListeners(maxListeners); - this.#contentScriptConnection = connection; this.#subscribe(); } @@ -104,19 +95,6 @@ export class CoreProvider extends EventEmitter { * Initializes provider state, and collects dApp information */ #init = async () => { - await this.#contentScriptConnection.connect(); - - onDomReady(async () => { - const domainMetadata = await getSiteMetadata(); - - this.#request({ - method: DAppProviderRequest.DOMAIN_METADATA_METHOD, - params: domainMetadata, - }); - - this.#providerReadyPromise.check(InitializationStep.DOMAIN_METADATA_SENT); - }); - try { const response = await this.#request({ method: DAppProviderRequest.INIT_DAPP_STATE, diff --git a/src/background/providers/initializeInpageProvider.ts b/src/background/providers/initializeInpageProvider.ts index be30cd57b..8e88905c4 100644 --- a/src/background/providers/initializeInpageProvider.ts +++ b/src/background/providers/initializeInpageProvider.ts @@ -24,7 +24,7 @@ export function initializeProvider( } ); - const provider = new Proxy(new CoreProvider({ maxListeners, connection }), { + const provider = new Proxy(new CoreProvider({ maxListeners }), { // some common libraries, e.g. web3@1.x, mess with our API deleteProperty: () => true, }); diff --git a/src/background/providers/utils/ProviderReadyPromise.ts b/src/background/providers/utils/ProviderReadyPromise.ts index 4db3e72da..fd8fb09b6 100644 --- a/src/background/providers/utils/ProviderReadyPromise.ts +++ b/src/background/providers/utils/ProviderReadyPromise.ts @@ -1,32 +1,37 @@ export enum InitializationStep { - DOMAIN_METADATA_SENT, - PROVIDER_STATE_LOADED, + DOMAIN_METADATA_SENT = 'domain_metadata_sent', + PROVIDER_STATE_LOADED = 'provider_state_loaded', } export class ProviderReadyPromise { - #steps: boolean[] = []; + #unpreparedSteps: InitializationStep[] = []; #inflightRequests: { resolve(value: unknown): void; fn(): Promise; }[] = []; - constructor() { - // length / 2 is required since InitializationStep is an enum - // enums generate objects like this: { key0: 0, key1: 1, 0: key0, 1: key1 } - this.#steps = Array(Object.keys(InitializationStep).length / 2).fill(false); + constructor(steps: InitializationStep[]) { + this.#unpreparedSteps = Object.values(steps); } check = (step: InitializationStep) => { - this.#steps[step] = true; + const stepIndex = this.#unpreparedSteps.findIndex( + (currentStep) => step === currentStep + ); + + if (stepIndex > -1) { + this.#unpreparedSteps.splice(stepIndex, 1); + } + this._proceed(); }; uncheck = (step: InitializationStep) => { - this.#steps[step] = false; + this.#unpreparedSteps[step] = step; }; private _proceed = () => { - if (this.#steps.some((step) => !step)) { + if (this.#unpreparedSteps.length) { return; } From 6cf8260d006754691a4127bc33c71078f13d19a1 Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Thu, 8 Aug 2024 18:33:37 +0200 Subject: [PATCH 8/9] test: align test cases --- .../providers/ChainAgnosticProvider.test.ts | 90 +++++- src/background/providers/CoreProvider.test.ts | 281 ++++++++---------- src/background/providers/CoreProvider.ts | 2 +- .../MultiWalletProviderProxy.test.ts | 28 +- .../initializeInpageProvider.test.ts | 5 +- .../providers/initializeInpageProvider.ts | 2 +- .../utils/ProviderReadyPromise.test.ts | 16 +- 7 files changed, 237 insertions(+), 187 deletions(-) diff --git a/src/background/providers/ChainAgnosticProvider.test.ts b/src/background/providers/ChainAgnosticProvider.test.ts index f2f4b78d1..59fb4715f 100644 --- a/src/background/providers/ChainAgnosticProvider.test.ts +++ b/src/background/providers/ChainAgnosticProvider.test.ts @@ -1,6 +1,22 @@ import { ethErrors } from 'eth-rpc-errors'; import AutoPairingPostMessageConnection from '../utils/messaging/AutoPairingPostMessageConnection'; import { ChainAgnosticProvider } from './ChainAgnosticProvider'; +import onDomReady from './utils/onDomReady'; +import { DAppProviderRequest } from '../connections/dAppConnection/models'; + +jest.mock('../utils/messaging/AutoPairingPostMessageConnection', () => { + const mocks = { + connect: jest.fn().mockResolvedValue(undefined), + on: jest.fn(), + request: jest.fn().mockResolvedValue({}), + }; + return jest.fn().mockReturnValue(mocks); +}); + +export const matchingPayload = (payload) => + expect.objectContaining({ + data: expect.objectContaining(payload), + }); jest.mock('./utils/onDomReady'); jest.mock('../utils/messaging/AutoPairingPostMessageConnection', () => { @@ -21,10 +37,15 @@ describe('src/background/providers/ChainAgnosticProvider', () => { expect(channelMock.connect).toHaveBeenCalled(); expect(channelMock.request).not.toHaveBeenCalled(); }); - it('waits for message channel to be connected', async () => { + it('should wait for message channel to be connected', async () => { const mockedChannel = new AutoPairingPostMessageConnection(false); const provider = new ChainAgnosticProvider(channelMock); + + await new Promise(process.nextTick); + + (onDomReady as jest.Mock).mock.calls[0][0](); + expect(mockedChannel.connect).toHaveBeenCalled(); expect(mockedChannel.request).not.toHaveBeenCalled(); @@ -35,13 +56,68 @@ describe('src/background/providers/ChainAgnosticProvider', () => { }); expect(mockedChannel.request).toHaveBeenCalled(); }); + it('should call the `DOMAIN_METADATA_METHOD` adter domReady', async () => { + new ChainAgnosticProvider(channelMock); + await new Promise(process.nextTick); + expect(channelMock.request).toHaveBeenCalledTimes(0); + (onDomReady as jest.Mock).mock.calls[0][0](); + await new Promise(process.nextTick); + + expect(channelMock.request).toHaveBeenCalledTimes(1); + + expect(channelMock.request).toHaveBeenCalledWith( + // matchingPayload({ + // method: DAppProviderRequest.INIT_DAPP_STATE, + // }) + expect.objectContaining({ + params: expect.objectContaining({ + request: expect.objectContaining({ + method: DAppProviderRequest.DOMAIN_METADATA_METHOD, + }), + }), + }) + ); + }); }); describe('request', () => { + it('should collect pending requests till the dom is ready', async () => { + const provider = new ChainAgnosticProvider(channelMock); + // wait for init to finish + await new Promise(process.nextTick); + + expect(channelMock.request).toHaveBeenCalledTimes(0); + + (channelMock.request as jest.Mock).mockResolvedValue('success'); + const rpcResultCallback = jest.fn(); + provider + .request({ + data: { + method: 'some-method', + params: [{ param1: 1 }], + }, + }) + .then(rpcResultCallback); + await new Promise(process.nextTick); + + expect(channelMock.request).toHaveBeenCalledTimes(0); + + // domReady triggers sending pending requests as well + (onDomReady as jest.Mock).mock.calls[0][0](); + await new Promise(process.nextTick); + + expect(channelMock.request).toHaveBeenCalledTimes(2); + + expect(rpcResultCallback).toHaveBeenCalledWith('success'); + }); it('should use the rate limits on `eth_requestAccounts` requests', async () => { const provider = new ChainAgnosticProvider(channelMock); (channelMock.request as jest.Mock).mockResolvedValue('success'); + await new Promise(process.nextTick); + + (onDomReady as jest.Mock).mock.calls[0][0](); + const firstCallCallback = jest.fn(); const secondCallCallback = jest.fn(); provider @@ -69,6 +145,10 @@ describe('src/background/providers/ChainAgnosticProvider', () => { const provider = new ChainAgnosticProvider(channelMock); (channelMock.request as jest.Mock).mockResolvedValue('success'); + await new Promise(process.nextTick); + + (onDomReady as jest.Mock).mock.calls[0][0](); + const firstCallCallback = jest.fn(); const secondCallCallback = jest.fn(); provider @@ -93,6 +173,10 @@ describe('src/background/providers/ChainAgnosticProvider', () => { const provider = new ChainAgnosticProvider(channelMock); (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); + await new Promise(process.nextTick); + + (onDomReady as jest.Mock).mock.calls[0][0](); + await provider.request({ data: { method: 'some-method', params: [{ param1: 1 }] }, sessionId: '00000000-0000-0000-0000-000000000000', @@ -106,6 +190,10 @@ describe('src/background/providers/ChainAgnosticProvider', () => { // response for the actual call (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); + await new Promise(process.nextTick); + + (onDomReady as jest.Mock).mock.calls[0][0](); + provider.request({ data: { method: 'some-method', params: [{ param1: 1 }] }, sessionId: '00000000-0000-0000-0000-000000000000', diff --git a/src/background/providers/CoreProvider.test.ts b/src/background/providers/CoreProvider.test.ts index 93698e67e..12dc55fb2 100644 --- a/src/background/providers/CoreProvider.test.ts +++ b/src/background/providers/CoreProvider.test.ts @@ -1,9 +1,9 @@ import { ethErrors } from 'eth-rpc-errors'; import { CoreProvider } from './CoreProvider'; -import onDomReady from './utils/onDomReady'; import { DAppProviderRequest } from '../connections/dAppConnection/models'; import AutoPairingPostMessageConnection from '../utils/messaging/AutoPairingPostMessageConnection'; import { EventNames } from './models'; +import { matchingPayload } from './ChainAgnosticProvider.test'; jest.mock('../utils/messaging/AutoPairingPostMessageConnection', () => { const mocks = { @@ -16,24 +16,18 @@ jest.mock('../utils/messaging/AutoPairingPostMessageConnection', () => { jest.mock('./utils/onDomReady'); -const matchingPayload = (payload) => - expect.objectContaining({ - data: expect.objectContaining(payload), - }); - +const channelMockResolvedValue = { + isUnlocked: true, + chainId: '0x1', + networkVersion: '1', + accounts: ['0x00000'], +}; describe('src/background/providers/CoreProvider', () => { const channelMock = new AutoPairingPostMessageConnection(false); const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); beforeEach(() => { jest.mocked(channelMock.connect).mockResolvedValueOnce(undefined); - - (channelMock.request as jest.Mock).mockResolvedValueOnce({ - isUnlocked: true, - chainId: '0x1', - networkVersion: '1', - accounts: ['0x00000'], - }); }); afterEach(() => { @@ -42,7 +36,7 @@ describe('src/background/providers/CoreProvider', () => { describe('EIP-5749', () => { it('sets the ProviderInfo', () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); expect(provider.info).toEqual({ description: 'EVM_PROVIDER_INFO_DESCRIPTION', icon: 'EVM_PROVIDER_INFO_ICON', @@ -56,7 +50,7 @@ describe('src/background/providers/CoreProvider', () => { describe('EIP-1193', () => { describe('request', () => { it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -80,7 +74,7 @@ describe('src/background/providers/CoreProvider', () => { ); // response for 'some-method' - (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); + (channelMock.request as jest.Mock).mockResolvedValue('success'); const rpcResultCallback = jest.fn(); provider .request({ @@ -90,14 +84,11 @@ describe('src/background/providers/CoreProvider', () => { .then(rpcResultCallback); await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); + expect(channelMock.request).toHaveBeenCalledTimes(2); - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); await new Promise(process.nextTick); - expect(channelMock.request).toHaveBeenCalledTimes(3); + expect(channelMock.request).toHaveBeenCalledTimes(2); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'some-method', @@ -109,7 +100,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('always returns JSON RPC-compatible error', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -132,8 +123,6 @@ describe('src/background/providers/CoreProvider', () => { }) ); - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce({}); // response for 'eth_requestAccounts' (channelMock.request as jest.Mock).mockRejectedValueOnce( new Error('non RPC error') @@ -146,14 +135,9 @@ describe('src/background/providers/CoreProvider', () => { .catch(callCallback); await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); await new Promise(process.nextTick); - expect(channelMock.request).toHaveBeenCalledTimes(3); + expect(channelMock.request).toHaveBeenCalledTimes(2); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'eth_requestAccounts', @@ -164,7 +148,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('does not double wraps JSON RPC errors', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -187,8 +171,6 @@ describe('src/background/providers/CoreProvider', () => { }) ); - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce({}); // response for 'eth_requestAccounts' (channelMock.request as jest.Mock).mockRejectedValueOnce({ code: 4902, @@ -204,14 +186,7 @@ describe('src/background/providers/CoreProvider', () => { .catch(callCallback); await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); + expect(channelMock.request).toHaveBeenCalledTimes(2); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'eth_requestAccounts', @@ -229,8 +204,11 @@ describe('src/background/providers/CoreProvider', () => { describe('events', () => { describe(`connect`, () => { - it('emits `connect` when chainId first set', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should emit `connect` when chainId first set', async () => { + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -246,7 +224,7 @@ describe('src/background/providers/CoreProvider', () => { const connectSubscription = jest.fn(); provider.addListener('connect', connectSubscription); - // wait for init to finish + // // wait for init to finish await new Promise(process.nextTick); expect(channelMock.request).toHaveBeenCalledTimes(1); @@ -260,7 +238,7 @@ describe('src/background/providers/CoreProvider', () => { expect(connectSubscription).toHaveBeenCalledWith({ chainId: '0x1' }); }); - it('does not emit connect if chain is still loading', async () => { + it('should not emit connect if chain is still loading', async () => { (channelMock.request as jest.Mock).mockReset(); (channelMock.request as jest.Mock).mockResolvedValue({ isUnlocked: true, @@ -268,7 +246,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -306,8 +284,11 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it('emits connect on re-connect after disconnected', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should emit connect on re-connect after disconnected', async () => { + const provider = new CoreProvider(); + (channelMock.request as jest.Mock).mockResolvedValue( + channelMockResolvedValue + ); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -354,7 +335,7 @@ describe('src/background/providers/CoreProvider', () => { describe('disconnect', () => { it('emits disconnect event with error', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -397,8 +378,8 @@ describe('src/background/providers/CoreProvider', () => { }); describe('chainChanged', () => { - it('does not emit `chainChanged` on initialization', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should not emit `chainChanged` on initialization', async () => { + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -428,7 +409,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -456,8 +437,12 @@ describe('src/background/providers/CoreProvider', () => { expect(chainChangedSubscription).toHaveBeenCalledWith('0x1'); }); - it('does not emit `chainChanged` when chain is set to the same value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should not emit `chainChanged` when chain is set to the same value', async () => { + const provider = new CoreProvider(); + + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -492,7 +477,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits `chainChanged` when chain is set to new value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -531,7 +516,7 @@ describe('src/background/providers/CoreProvider', () => { describe('accountsChanged', () => { it('emits `accountsChanged` on initialization', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -564,7 +549,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: '1', accounts: undefined, }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -587,8 +572,12 @@ describe('src/background/providers/CoreProvider', () => { expect(accountsChangedSubscription).toHaveBeenCalledWith([]); }); - it('does not emit `accountsChanged` when account is set to the same value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should not emit `accountsChanged` when account is set to the same value', async () => { + const provider = new CoreProvider(); + + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -617,8 +606,12 @@ describe('src/background/providers/CoreProvider', () => { expect(accountsChangedSubscription).toHaveBeenCalledTimes(1); }); - it('emits `accountsChanged` when account is set to new value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should emit `accountsChanged` when account is set to new value', async () => { + const provider = new CoreProvider(); + + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -654,8 +647,8 @@ describe('src/background/providers/CoreProvider', () => { describe('legacy', () => { describe('sendAsync', () => { - it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should call the requests correctly', async () => { + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -671,10 +664,10 @@ describe('src/background/providers/CoreProvider', () => { // wait for init to finish await new Promise(process.nextTick); - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce(undefined); // response for 'some-method' (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); + // response for domain metadata send + (channelMock.request as jest.Mock).mockResolvedValueOnce(undefined); const rpcResultCallback = jest.fn(); provider.sendAsync( { @@ -686,13 +679,8 @@ describe('src/background/providers/CoreProvider', () => { await new Promise(process.nextTick); // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); + expect(channelMock.request).toHaveBeenCalledTimes(2); - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'some-method', @@ -706,8 +694,8 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it('supports batched requets', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should support batched requets', async () => { + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -739,16 +727,10 @@ describe('src/background/providers/CoreProvider', () => { ], rpcResultCallback ); - await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); await new Promise(process.nextTick); - expect(channelMock.request).toHaveBeenCalledTimes(4); + expect(channelMock.request).toHaveBeenCalledTimes(3); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'some-method', @@ -770,8 +752,8 @@ describe('src/background/providers/CoreProvider', () => { }); describe('send', () => { - it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should call the requests properly', async () => { + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -787,8 +769,6 @@ describe('src/background/providers/CoreProvider', () => { // wait for init to finish await new Promise(process.nextTick); - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce({}); // response for 'some-method' (channelMock.request as jest.Mock).mockResolvedValueOnce('success'); const rpcResultCallback = jest.fn(); @@ -801,14 +781,8 @@ describe('src/background/providers/CoreProvider', () => { ); await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); + expect(channelMock.request).toHaveBeenCalledTimes(2); - expect(channelMock.request).toHaveBeenCalledTimes(3); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'some-method', @@ -822,8 +796,8 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it('supports batched requets', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should support batched requets', async () => { + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -855,16 +829,10 @@ describe('src/background/providers/CoreProvider', () => { ], rpcResultCallback ); - await new Promise(process.nextTick); - - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); await new Promise(process.nextTick); - expect(channelMock.request).toHaveBeenCalledTimes(4); + expect(channelMock.request).toHaveBeenCalledTimes(3); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'some-method', @@ -884,8 +852,8 @@ describe('src/background/providers/CoreProvider', () => { ]); }); - it('supports method as the only param', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should support method as the only param', async () => { + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -911,14 +879,7 @@ describe('src/background/providers/CoreProvider', () => { await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); + expect(channelMock.request).toHaveBeenCalledTimes(2); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'some-method', @@ -932,8 +893,8 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it('supports method with params', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should support method with params', async () => { + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -959,14 +920,7 @@ describe('src/background/providers/CoreProvider', () => { await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); + expect(channelMock.request).toHaveBeenCalledTimes(2); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'some-method', @@ -980,8 +934,13 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it('returns eth_accounts response syncronously', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should return eth_accounts response syncronously', async () => { + const provider = new CoreProvider(); + + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1007,8 +966,12 @@ describe('src/background/providers/CoreProvider', () => { }); }); - it('returns eth_coinbase response syncronously', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should return eth_coinbase response syncronously', async () => { + const provider = new CoreProvider(); + + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1035,7 +998,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('throws error if method not supported syncronously', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1070,8 +1033,12 @@ describe('src/background/providers/CoreProvider', () => { }); describe('enable', () => { - it('collects pending requests till the dom is ready', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should call the requests properly', async () => { + const provider = new CoreProvider(); + + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1087,22 +1054,13 @@ describe('src/background/providers/CoreProvider', () => { // wait for init to finish await new Promise(process.nextTick); - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce(undefined); // response for 'some-method' (channelMock.request as jest.Mock).mockResolvedValueOnce(['0x0000']); const rpcResultCallback = jest.fn(); provider.enable().then(rpcResultCallback); await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); + expect(channelMock.request).toHaveBeenCalledTimes(2); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'eth_requestAccounts', @@ -1115,7 +1073,8 @@ describe('src/background/providers/CoreProvider', () => { describe('net_version', () => { it('supports net_version call', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1131,22 +1090,13 @@ describe('src/background/providers/CoreProvider', () => { // wait for init to finish await new Promise(process.nextTick); - // response for domain metadata send - (channelMock.request as jest.Mock).mockResolvedValueOnce(undefined); // response for 'some-method' (channelMock.request as jest.Mock).mockResolvedValueOnce('1'); const rpcResultCallback = jest.fn(); provider.net_version().then(rpcResultCallback); await new Promise(process.nextTick); - // no domReady happened yet, still only one call sent - expect(channelMock.request).toHaveBeenCalledTimes(1); - - // domReady triggers sending pending requests as well - (onDomReady as jest.Mock).mock.calls[0][0](); - await new Promise(process.nextTick); - - expect(channelMock.request).toHaveBeenCalledTimes(3); + expect(channelMock.request).toHaveBeenCalledTimes(2); expect(channelMock.request).toHaveBeenCalledWith( matchingPayload({ method: 'net_version', @@ -1158,8 +1108,12 @@ describe('src/background/providers/CoreProvider', () => { }); describe('close event', () => { - it('emits close event with error', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should emit close event with error', async () => { + const provider = new CoreProvider(); + + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1202,8 +1156,9 @@ describe('src/background/providers/CoreProvider', () => { }); describe('networkChanged event', () => { - it('does not emit `networkChanged` on initialization', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should not emit `networkChanged` on initialization', async () => { + const provider = new CoreProvider(); + (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1233,7 +1188,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: 'loading', accounts: ['0x00000'], }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1262,8 +1217,12 @@ describe('src/background/providers/CoreProvider', () => { expect(networkChangedSubscription).toHaveBeenCalledWith('1'); }); - it('does not emit `networkChanged` when chain is set to the same value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + it('should not emit `networkChanged` when chain is set to the same value', async () => { + const provider = new CoreProvider(); + + (channelMock.request as jest.Mock).mockResolvedValueOnce( + channelMockResolvedValue + ); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1298,7 +1257,7 @@ describe('src/background/providers/CoreProvider', () => { }); it('emits `chainChanged` when chain is set to new value', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1339,7 +1298,7 @@ describe('src/background/providers/CoreProvider', () => { describe('init', () => { it('should call the event listener with the right event name', async () => { - new CoreProvider({ connection: channelMock }); + new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1369,7 +1328,7 @@ describe('src/background/providers/CoreProvider', () => { networkVersion: '1', accounts: ['0x00000'], }); - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1408,7 +1367,7 @@ describe('src/background/providers/CoreProvider', () => { describe('Metamask compatibility', () => { it('supports _metamask.isUnlocked', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); (addEventListenerSpy.mock.calls[0]?.[1] as any)({ detail: { provider: { @@ -1428,11 +1387,11 @@ describe('src/background/providers/CoreProvider', () => { expect(await provider._metamask.isUnlocked()).toBe(true); }); it('isMetamask is true', () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); expect(provider.isMetaMask).toBe(true); }); it('isAvalanche is true', async () => { - const provider = new CoreProvider({ connection: channelMock }); + const provider = new CoreProvider(); expect(provider.isAvalanche).toBe(true); }); }); diff --git a/src/background/providers/CoreProvider.ts b/src/background/providers/CoreProvider.ts index 8fbc56739..4e497da0f 100644 --- a/src/background/providers/CoreProvider.ts +++ b/src/background/providers/CoreProvider.ts @@ -66,7 +66,7 @@ export class CoreProvider extends EventEmitter { isUnlocked: () => Promise.resolve(this._isUnlocked), }; - constructor({ maxListeners = 100 }: { maxListeners?: number }) { + constructor(maxListeners: number = 100) { super(); this.setMaxListeners(maxListeners); this.#subscribe(); diff --git a/src/background/providers/MultiWalletProviderProxy.test.ts b/src/background/providers/MultiWalletProviderProxy.test.ts index 31e8b4dac..63b8820a4 100644 --- a/src/background/providers/MultiWalletProviderProxy.test.ts +++ b/src/background/providers/MultiWalletProviderProxy.test.ts @@ -4,7 +4,6 @@ import { MultiWalletProviderProxy, createMultiWalletProxy, } from './MultiWalletProviderProxy'; -import AutoPairingPostMessageConnection from '../utils/messaging/AutoPairingPostMessageConnection'; jest.mock('../utils/messaging/AutoPairingPostMessageConnection'); jest.mock('./CoreProvider', () => ({ @@ -16,10 +15,9 @@ jest.mock('./CoreProvider', () => ({ })); describe('src/background/providers/MultiWalletProviderProxy', () => { - const connectionMock = new AutoPairingPostMessageConnection(false); describe('init', () => { it('initializes with the default provider', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); @@ -45,7 +43,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('addProvider', () => { it('adds new providers from coinbase proxy', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -72,7 +70,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not add extra coinbase proxy', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -89,7 +87,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('adds new provider', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -106,7 +104,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('wallet selection', () => { it('toggles wallet selection on `eth_requestAccounts` call if multiple providers', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -153,7 +151,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not toggle wallet selection if only core is available', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = new MultiWalletProviderProxy(provider); provider.request = jest.fn().mockResolvedValueOnce(['0x000000']); @@ -176,7 +174,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('does not toggle wallet selection if wallet is already selected', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -232,7 +230,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: enable', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, enable: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -274,7 +272,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: sendAsync', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -328,7 +326,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('wallet selection works with legacy functions: send with callback', async () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const provider2 = { isMetaMask: true, request: jest.fn() }; const mwpp = new MultiWalletProviderProxy(provider); mwpp.addProvider(provider2); @@ -380,7 +378,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { describe('createMultiWalletProxy', () => { it('creates proxy with property deletion disabled', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = createMultiWalletProxy(provider); expect(mwpp.defaultProvider).toBe(provider); @@ -389,7 +387,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('allows setting extra params without changing the provider', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = createMultiWalletProxy(provider); (mwpp as any).somePromerty = true; @@ -413,7 +411,7 @@ describe('src/background/providers/MultiWalletProviderProxy', () => { }); it('maintains the providers list properly', () => { - const provider = new CoreProvider({ connection: connectionMock }); + const provider = new CoreProvider(); const mwpp = createMultiWalletProxy(provider); const fooMock = () => 'bar'; const bizMock = () => 'baz'; diff --git a/src/background/providers/initializeInpageProvider.test.ts b/src/background/providers/initializeInpageProvider.test.ts index 6b47d4ddd..d5e9d386e 100644 --- a/src/background/providers/initializeInpageProvider.test.ts +++ b/src/background/providers/initializeInpageProvider.test.ts @@ -35,10 +35,7 @@ describe('src/background/providers/initializeInpageProvider', () => { it('initializes CoreProvider with the correct channel name', () => { const provider = initializeProvider(connectionMock, 10, windowMock); - expect(CoreProvider).toHaveBeenCalledWith({ - connection: connectionMock, - maxListeners: 10, - }); + expect(CoreProvider).toHaveBeenCalledWith(10); expect(provider.isAvalanche).toBe(true); }); diff --git a/src/background/providers/initializeInpageProvider.ts b/src/background/providers/initializeInpageProvider.ts index 8e88905c4..f57466e6f 100644 --- a/src/background/providers/initializeInpageProvider.ts +++ b/src/background/providers/initializeInpageProvider.ts @@ -24,7 +24,7 @@ export function initializeProvider( } ); - const provider = new Proxy(new CoreProvider({ maxListeners }), { + const provider = new Proxy(new CoreProvider(maxListeners), { // some common libraries, e.g. web3@1.x, mess with our API deleteProperty: () => true, }); diff --git a/src/background/providers/utils/ProviderReadyPromise.test.ts b/src/background/providers/utils/ProviderReadyPromise.test.ts index d8071e715..3c92d1476 100644 --- a/src/background/providers/utils/ProviderReadyPromise.test.ts +++ b/src/background/providers/utils/ProviderReadyPromise.test.ts @@ -5,7 +5,9 @@ import { describe('src/background/providers/utils/ProviderReadyPromise', () => { it('calls immediately if all checks are checked', async () => { - const initializedPromise = new ProviderReadyPromise(); + const initializedPromise = new ProviderReadyPromise([ + InitializationStep.DOMAIN_METADATA_SENT, + ]); initializedPromise.check(InitializationStep.DOMAIN_METADATA_SENT); initializedPromise.check(InitializationStep.PROVIDER_STATE_LOADED); @@ -17,7 +19,10 @@ describe('src/background/providers/utils/ProviderReadyPromise', () => { }); it('calls pending requests when last check is checked', async () => { - const initializedPromise = new ProviderReadyPromise(); + const initializedPromise = new ProviderReadyPromise([ + InitializationStep.DOMAIN_METADATA_SENT, + InitializationStep.PROVIDER_STATE_LOADED, + ]); initializedPromise.check(InitializationStep.DOMAIN_METADATA_SENT); const callMock = jest.fn(); @@ -31,7 +36,10 @@ describe('src/background/providers/utils/ProviderReadyPromise', () => { }); it('suspends calls when a check is unckecked', async () => { - const initializedPromise = new ProviderReadyPromise(); + const initializedPromise = new ProviderReadyPromise([ + InitializationStep.DOMAIN_METADATA_SENT, + InitializationStep.PROVIDER_STATE_LOADED, + ]); initializedPromise.check(InitializationStep.DOMAIN_METADATA_SENT); initializedPromise.check(InitializationStep.PROVIDER_STATE_LOADED); @@ -43,7 +51,7 @@ describe('src/background/providers/utils/ProviderReadyPromise', () => { initializedPromise.uncheck(InitializationStep.DOMAIN_METADATA_SENT); initializedPromise.call(callMock); - expect(callMock).toHaveBeenCalledTimes(1); + expect(callMock).toHaveBeenCalledTimes(2); initializedPromise.check(InitializationStep.DOMAIN_METADATA_SENT); From 616df33e1ddac3100cfc27d1ee43f39b74ff49fe Mon Sep 17 00:00:00 2001 From: Viktor Vasas Date: Thu, 22 Aug 2024 14:58:19 +0200 Subject: [PATCH 9/9] fix: core wallet test, provider ready promise --- .../initializeInpageProvider.test.ts | 16 ++++++++++++--- .../utils/ProviderReadyPromise.test.ts | 3 +-- .../providers/utils/ProviderReadyPromise.ts | 20 +++++++++---------- 3 files changed, 23 insertions(+), 16 deletions(-) diff --git a/src/background/providers/initializeInpageProvider.test.ts b/src/background/providers/initializeInpageProvider.test.ts index d5e9d386e..259bfd796 100644 --- a/src/background/providers/initializeInpageProvider.test.ts +++ b/src/background/providers/initializeInpageProvider.test.ts @@ -185,9 +185,6 @@ describe('src/background/providers/initializeInpageProvider', () => { expect(windowMock.dispatchEvent.mock.calls[3][0].type).toEqual( 'eip6963:announceProvider' ); - expect(windowMock.dispatchEvent.mock.calls[4][0].type).toEqual( - 'core-wallet:announceProvider' - ); expect(windowMock.dispatchEvent.mock.calls[3][0].detail).toEqual({ info: provider.info, provider: provider, @@ -211,6 +208,19 @@ describe('src/background/providers/initializeInpageProvider', () => { expect(windowMock.dispatchEvent.mock.calls[3][0].type).toEqual( 'eip6963:announceProvider' ); + }); + }); + describe('core-wallet ', () => { + it('should announce chainagnostic provider with core-wallet:announceProvider', () => { + initializeProvider(connectionMock, 10, windowMock); + + expect(windowMock.dispatchEvent.mock.calls[4][0].type).toEqual( + 'core-wallet:announceProvider' + ); + }); + it('should re-announce on core-wallet:requestProvider', () => { + initializeProvider(connectionMock, 10, windowMock); + expect(windowMock.dispatchEvent.mock.calls[4][0].type).toEqual( 'core-wallet:announceProvider' ); diff --git a/src/background/providers/utils/ProviderReadyPromise.test.ts b/src/background/providers/utils/ProviderReadyPromise.test.ts index 3c92d1476..e9550557f 100644 --- a/src/background/providers/utils/ProviderReadyPromise.test.ts +++ b/src/background/providers/utils/ProviderReadyPromise.test.ts @@ -51,8 +51,7 @@ describe('src/background/providers/utils/ProviderReadyPromise', () => { initializedPromise.uncheck(InitializationStep.DOMAIN_METADATA_SENT); initializedPromise.call(callMock); - expect(callMock).toHaveBeenCalledTimes(2); - + expect(callMock).toHaveBeenCalledTimes(1); initializedPromise.check(InitializationStep.DOMAIN_METADATA_SENT); await new Promise(process.nextTick); diff --git a/src/background/providers/utils/ProviderReadyPromise.ts b/src/background/providers/utils/ProviderReadyPromise.ts index fd8fb09b6..e877d73a3 100644 --- a/src/background/providers/utils/ProviderReadyPromise.ts +++ b/src/background/providers/utils/ProviderReadyPromise.ts @@ -4,34 +4,32 @@ export enum InitializationStep { } export class ProviderReadyPromise { - #unpreparedSteps: InitializationStep[] = []; + #unpreparedSteps: Map = new Map(); #inflightRequests: { resolve(value: unknown): void; - fn(): Promise; + fn(): Promise; }[] = []; constructor(steps: InitializationStep[]) { - this.#unpreparedSteps = Object.values(steps); + steps.map((step) => this.#unpreparedSteps.set(step, true)); } check = (step: InitializationStep) => { - const stepIndex = this.#unpreparedSteps.findIndex( - (currentStep) => step === currentStep - ); + const hasStep = this.#unpreparedSteps.has(step); - if (stepIndex > -1) { - this.#unpreparedSteps.splice(stepIndex, 1); + if (hasStep) { + this.#unpreparedSteps.delete(step); } this._proceed(); }; uncheck = (step: InitializationStep) => { - this.#unpreparedSteps[step] = step; + this.#unpreparedSteps.set(step, true); }; private _proceed = () => { - if (this.#unpreparedSteps.length) { + if (this.#unpreparedSteps.size) { return; } @@ -41,7 +39,7 @@ export class ProviderReadyPromise { } }; - call = (fn) => { + call = (fn: () => Promise) => { return new Promise((resolve) => { this.#inflightRequests.push({ fn,